5

When i create several instances of a react class (by using React.createElement on the same class), some member variables are shared between the instances (arrays and objects are shared, strings and booleans etc. not).

For me this feels horrible and scary and wrong. Is this a bug or is there another way to do what i want to do?

Please have a look: http://jsbin.com/kanayiguxu/1/edit?html,js,console,output

tbodt
  • 16,609
  • 6
  • 58
  • 83
delijah
  • 83
  • 2
  • 3
  • 7
  • 2
    "Questions seeking debugging help ("why isn't this code working?") must include the desired behavior, a specific problem or error and the shortest code necessary to reproduce it ***in the question itself***. Questions without a clear problem statement are not useful to other readers. See: How to create a [Minimal, Complete, and Verifiable example](/help/mcve)." – T.J. Crowder Apr 17 '15 at 10:57
  • I suggest you post the (relevant) parts of the code on SO, and you might also want to check out JavaScript docs on scope in JavaScript: http://www.smashingmagazine.com/2009/08/01/what-you-need-to-know-about-javascript-scope/ – Ted Nyberg Apr 17 '15 at 10:58
  • 1
    @TedNyberg: From the description, this has nothing to do with scope. It has to do with the difference between primitives and object references on prototypes, most likely. – T.J. Crowder Apr 17 '15 at 10:59
  • @T.J.Crowder That sounds about right. Could recommend the following link to the OP: http://stackoverflow.com/questions/518000/is-javascript-a-pass-by-reference-or-pass-by-value-language – Ted Nyberg Apr 17 '15 at 11:05

1 Answers1

11

What you should be doing is setting state on your component, instead of having state as arbitrary properties on your React component.

So instead of doing this:

var MyComponent = React.createClass({
  myArray: [1, 2, 3],
  componentWillMount() {
    this.myArray.push(this.myArray.length + 1);
  },
  render() {
    return (
      <span>{this.myArray.length}</span>
    );
  }
});

You should be doing this:

var MyComponent = React.createClass({
  getInitialState() {
    return {
      myArray: [1, 2, 3]
    };
  },
  componentWillMount() {
    this.setState(state => {
      state.myArray.push(state.myArray.length + 1);
      return state;
    });
  },
  render() {
    return (
      <span>{this.myArray.length}</span>
    );
  }
});

The reason being that all of a components state and data should reside in this.state and this.props which is controlled and handled by React.

The benefit you get from using props and state for this, is that React will know when those change, and from that it can tell when it's time to re-render your component. If you store state as arbitrary properties or globals, React won't know when those change, and cannot re-render for you.

The reason for the behaviour you're seeing is that every instance of the component uses the object you give to React.createClass() as its prototype. So all instances of the component has a myArray property, but that is on the prototype chain, and thus shared by all instances.

If you truly want something like this and you want to avoid this.state, you should use something like componentWillMount and inside that method, assign properties to this. This will make sure that such data is only on that particular instance, and not on the prototype chain.

EDIT

To even further clearify, it can be good to know that the object passed to React.createClass() isn't the actual object on the prototype. What React does is that it iterates over all properties on that object, and copies them onto the prototype of the React element object. This can be illustrated by this example:

var obj = {
  myArray: [1, 2, 3],
  title: 'My title',
  componentWillMount() {
    this.myArray.push(this.myArray.length + 1);
  },
  render() {
    return (
      <span>{this.myArray.length}</span>
    );
  }
}

var MyComponent = React.createClass(obj);

// This doesn't change the component, since 'obj' isn't used anymore
// by React, it has already copied all properties.
obj.title = 'New title';

// This however affects the component, because the reference to the array
// was copied to the component prototype, and any changes to what the 
// reference points to will affect everyone who has access to it.
obj.myArray.push(666);
Anders Ekdahl
  • 22,685
  • 4
  • 70
  • 59
  • Thanks for your quick reply anders! To assign properties to "this" in "componentWillMount" function sounds quite "hackish" to me. But in the same way, in my use case, the variable does also not feel like a prop or a state of a component. In my case i have a messages array in state and want to create an index of messages to access them faster or check for their existence faster (messagesIndex). I'll give you another example: http://jsbin.com/guwipeyubu/1/edit?html,js,console,output – delijah Apr 17 '15 at 13:29
  • 1
    Yes well the reason it feels hackish is that it's not very idiomatic and not something you're "supposed" to do. I can't really grasp what your example is supposed to do, but as a general rule of thumb; if the data is something that is supposed to be rendered by the component, it should definitely be a prop or part of the state. And if it's not something that should get rendered (that is, used in the render method), it probably doesn't belong in the component. – Anders Ekdahl Apr 17 '15 at 13:42
  • Please accept this answer if it addressed your question satisfactorily. And its a great answer by the way. – hazardous Jul 05 '16 at 11:38
  • Anders answer should be accepted. The question raised in the post has been answered. – Zeena Apr 14 '18 at 02:29