What is the proper way to getInitialState with remote data in Reactjs?

2.2k Views Asked by At

IT IS SOLVED. The code was ok, the problem was with improper import.

It is a long post (due to code samples). I'll appreciate your patience and will be very thankful for your help!

We have a RoR back-end and React on the front-end and we are using alt as an implementation of flux. We also use babel to compile ES6 to ES5. So the problem is that I can not render component due to 2 errors.

First is Uncaught TypeError: Cannot read property 'map' of undefined

It appears on the render function of MapPalette component:

render() {
  return (
    <div>
      {this.state.featureCategories.map(fc => <PaletteItemsList featureCategory={fc} />)}
    </div>
    );
}

And the second is Uncaught Error: Invariant Violation: receiveComponent(...): Can only update a mounted component.

So here is the whole MapPalette component

"use strict";
import React from 'react';
import PaletteItemsList from './PaletteItemsList';
import FeatureCategoryStore from '../stores/FeatureTypeStore';

function getAppState() {
  return {
    featureCategories: FeatureCategoryStore.getState().featureCategories
  };
}

var MapPalette = React.createClass({
  displayName: 'MapPalette',

  propTypes: {
    featureSetId: React.PropTypes.number.isRequired
  },

  getInitialState() {
    return getAppState();
  },

  componentDidMount() {
    FeatureCategoryStore.listen(this._onChange);
  },

  componentWillUnmount() {
    FeatureCategoryStore.unlisten(this._onChange);
  },

  render() {
    return (
      <div>
        {this.state.featureCategories.map(fc => <PaletteItemsList featureCategory={fc} />)}
      </div>
      );
  },

  _onChange() {
    this.setState(getAppState());
  }

});

module.exports = MapPalette;

FeatureCategoryStore

var featureCategoryStore = alt.createStore(class FeatureCategoryStore {
  constructor() {
    this.bindActions(FeatureCategoryActions)
    this.featureCategories = [];
  }

  onReceiveAll(featureCategories) {
    this.featureCategories = featureCategories;
  }

})

module.exports = featureCategoryStore

FeatureCategoryActions

class FeatureCategoryActions {
  receiveAll(featureCategories) {
    this.dispatch(featureCategories)
  }

  getFeatureSetCategories(featureSetId) {
    var url = '/feature_categories/nested_feature_types.json';
    var actions = this.actions;
    this.dispatch();

    request.get(url)
           .query({ feature_set_id: featureSetId })
           .end( function(response) {
             actions.receiveAll(response.body);
           });
  }
}

module.exports = alt.createActions(FeatureCategoryActions);

And the last - how I render React component.

var render = function() {
    FeatureCategoryActions.getFeatureSetCategories(#{ @feature_set.id });
    React.render(
      React.createElement(FeatureSetEditMap, {featureSetId: #{@feature_set.id}}),
      document.getElementById('react-app')
    )
  }
2

There are 2 best solutions below

0
Danny Ocean On BEST ANSWER

Sorry for your wasted time on reading it, but I figured it out and the reason of trouble was my silly mistake in importing. Essentially, I've imported another Store with the name of needed.

So, instead of import FeatureCategoryStore from '../stores/FeatureTypeStore';

It should be import FeatureCategoryStore from '../stores/FeatureCategoryStore';

7
Anders Ekdahl On

First of all, the first error you get:

Uncaught TypeError: Cannot read property 'map' of undefined

Is because this.state in your component is undefined, which means that you probably haven't implemented getInitialState in your component.

You haven't included the full implementation of your component, which we need to see to be able to help you. But let's walk through their example of a view component:

var LocationComponent = React.createClass({
  getInitialState() {
    return locationStore.getState()
  },

  componentDidMount() {
    locationStore.listen(this.onChange)
  },

  componentWillUnmount() {
    locationStore.unlisten(this.onChange)
  },

  onChange() {
    this.setState(this.getInitialState())
  },

  render() {
    return (
      <div>
        <p>
          City {this.state.city}
        </p>
        <p>
          Country {this.state.country}
        </p>
      </div>
    )
  }
})

They implement getInitialState here to return the current state of the store, which you'll then be able to use in the render method. In componentDidMount, they listen to change events from that store so that any events occuring in that store coming from anywhere in your application will trigger a re-render. componentWillUnmount cleans up the event listener. Very important not to forget this, or your app will leak memory! Next the onChange method (which could be have whatever name, it's not an internal React method), which the store will call when a change event occurs. This just sets the state of the component to whatever the stores state is. Might be a bit confusing that they call getInitialState again here, because you're not getting the initial state, you're getting the current state of the store.

Another important note here is that this example won't work off the bat with ES6/ES2015 classes, because React no longer autobinds methods to the instance of the component. So the example implemented as a class would look something like this:

class LocationComponent extends React.Component {
  constructor(props) {
    super(props)
    this.state = this.getState();

    this.onChangeListener = () => this.setState(this.getState());
  }

  getState() {
    return locationStore.getState();
  }

  componentDidMount() {
    locationStore.listen(this.onChangeListener);
  }

  componentWillUnmount() {
    locationStore.unlisten(this.onChangeListener)
  }

  render() {
    return (
      <div>
        <p>
          City {this.state.city}
        </p>
        <p>
          Country {this.state.country}
        </p>
      </div>
    );
  }
}