1

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')
    )
  }
000
  • 26,951
  • 10
  • 71
  • 101
Danny Ocean
  • 1,081
  • 2
  • 14
  • 30
  • firstly, prepare the loop outside of the return block, ie create a new variable and then save the contents of `this.state.featureCategories.map(fc => ` in the render function but not in the return block. Then use that variable inside the
    of the return part
    – Koustuv Sinha Apr 23 '15 at 05:23
  • @KoustuvSinha Why would you do that? I think it reads much better the way it's done now. – Anders Ekdahl Apr 23 '15 at 06:09
  • it reads better, yes but it would be easier to debug if you construct the array before returning thats all – Koustuv Sinha Apr 23 '15 at 06:22
  • 1
    That's a good point, even if I wouldn't structure my code for ease of debugging. You might want to edit the comment to reflect that, because currently your comment reads as if that is something related to OPs problem. – Anders Ekdahl Apr 23 '15 at 06:27

2 Answers2

2

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>
    );
  }
}
Anders Ekdahl
  • 22,685
  • 4
  • 70
  • 59
  • great explanation! can you share a fiddle? :) – Koustuv Sinha Apr 23 '15 at 06:33
  • Thanks for explanations! I've updated my question with full implementation of the component. I do not understand why the state is undefined. Because from the console I can see that this call `FeatureCategoryActions.getFeatureSetCategories(#{ @feature_set.id });` Sends a request and getting expected response. – Danny Ocean Apr 23 '15 at 07:47
  • Try adding `console.log(FeatureCategoryStore.getState().featureCategories)` in your `getAppState` function to see that it contains what you expect. – Anders Ekdahl Apr 23 '15 at 07:51
  • Then that's the reason you get the first error. Does `console.log(FeatureCategoryStore.getState())` yield undefined as well? – Anders Ekdahl Apr 23 '15 at 07:57
  • Nope, It show and object. FeatureCategory object. I thought it should be an array with that object? – Danny Ocean Apr 23 '15 at 07:59
  • I haven't used alt and don't know enough about your code to answer that unfortunately. – Anders Ekdahl Apr 23 '15 at 08:01
  • Okay. Anyway, thank you very much for your help, patience and attention! – Danny Ocean Apr 23 '15 at 08:03
0

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';

Danny Ocean
  • 1,081
  • 2
  • 14
  • 30