Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Member variables in react class are "shared" by reference

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

like image 791
delijah Avatar asked Apr 17 '15 10:04

delijah


1 Answers

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);
like image 150
Anders Ekdahl Avatar answered Nov 14 '22 21:11

Anders Ekdahl