Last updated at Mon, 04 Dec 2017 19:57:10 GMT

A central feature of the React framework is that a component will re-render when its properties change. Additional action, or deliberate inaction, can also be taken on a change of properties using componentWillRecieveProps() -- at which point you’ll do your own comparison of the new and old props. In both cases, if the two properties in question are objects, the comparison is not so straightforward. How do I easily modify and compare javascript objects by some set of their inner fields?

Note: Immutable.js is a great solution to this problem. It allows you to create immutable objects and provides utilities for doing easy comparison and duplication -- to name only a few features. But as software engineers, we are often developing within constraints. You can’t always leverage new libraries at will. With that in mind, the following patterns can serve as a decent solution to the problem without introducing any new libraries.

Key understanding: Javascript objects are not immutable, and this must be considered when modifying and comparing objects.

Here’s an example of how this can come into play with property comparison.

ComponentA:

class ComponentA extends React.Component {
  constructor(props) {
    super(props);
    this.updateStateObject = this.updateStateObject.bind(this);

    this.state = {
      objectA: {
          field1: 'value1',
      }
    };
  }

  // Updates the this.state.objectA.field1 to 'value2'
  updateStateObject() {
    const newObjectA = this.state.objectA;
    newObjectA.field1 = 'value2';
    this.setState({
      objectA: newObjectA,
    });
  }

  render() {
    return (
      <ComponentB
        prop1={this.state.objectA}
        prop2={this.updateStateObject}
      />
    );
  }
}

ComponentB:

class ComponentB extends React.Component {
  // This method fires everytime ComponentB's props change
  componentWillReceiveProps(newProps) {
    // Print out the new and old props
    console.log(`old props: ${this.props.prop1.field1}`);
    console.log(`new props: ${newProps.prop1.field1}`);
  }

  render() {
    return (
      <div onClick={this.props.prop2}>
        {this.props.prop1.field1}
      </div>
    );
  }
}

ComponentB.propTypes = {
  prop1: React.PropTypes.obj,
  prop2: React.PropTypes.func,
};

When ComponentA renders, it will render ComponentB, passing in its this.state.objectA as ComponentB's prop1 property and its updateStateObject() method as prop2. Although ComponentB is receiving properties, its componentWillReceiveProps() will not fire since the method is not called on the initial render of a component.

Now let's say we want to call ComponentA.updateStateObject()  by clicking on our rendered div from ComponentB (the function has been set to the div's onClick attribute). This will update the value at objectA.field1 to 'value2'.  At this point, ComponentA will re-render, also re-rendering ComponentB with new props. This will fire ComponentB’s componentWillReceiveProps() method, printing out prop1.field1 for the new and old props with the following output:

old props: 'value2' new props: 'value2'

Wait, I thought we were printing the new and old props here, so why are the values the same?

This is because the new and old props variables are pointing to the same object in memory. If we look at updateStateObject(), notice how we don’t create a new object.

class ComponentA extends React.Component {
  constructor(props) {
    super(props);
    this.updateStateObject = this.updateStateObject.bind(this);

    this.state = {
      objectA: {
          field1: 'value1',
      }
    };
  }

  // Updates the this.state.objectA.field1 to 'value2'
  updateStateObject() {
    // newObjectA is just a pointer to this.state.objectA and is not a new object.
    // The variable used in ComponentB is still pointing to this same location in memory.
    const newObjectA = this.state.objectA;
    // When we modify newObjectA, we are also modifying the value referenced in ComponentB
    newObjectA.field1 = 'value2';
    this.setState({
      objectA: newObjectA,
    });
  }

  render() {
    return (
      <ComponentB
        prop1={this.state.objectA}
        prop2={this.updateStateObject}
      />
    );
  }
}

In order to avoid this, instead of creating a new pointer to this.state.objectA, we actually want to create (and modify) an entirely new object that is a copy of this.state.objectA.

How do I easily create a copy of an object?

A quick and dirty way to do this is to stringify your object, and then parse that JSON string back into a brand new object. I recommend creating a utility function, since you’ll likely want to re-use this throughout your app. Below is an example:

function deepCopy(obj) {
  if (obj !== undefined && obj !== null) {
    return JSON.parse(JSON.stringify(obj));
  }

  return null;
}

And here's the correct way to set the value in ComponentA:

updateStateObject() {
  const newObjectA = deepCopy(this.state.objectA);
  newObjectA.field1 = 'value2';
  this.setState({
    objectA: newObjectA,
  });
}

Running through the previous scenario again with the updated code, the output of ComponentB's componentWillReceiveProps() on the first re-render will be what we expect:

old props: 'value1' new props: 'value2'

Now that I correctly created two distinct objects for comparison, how do I go about comparing them?

The simplest way would be to directly compare individual fields in an if statement.

componentWillReceiveProps(newProps) {
  if (this.props.prop1.field1 === newProps.prop1.field1) {
    // Do stuff here
  }
}

However, if the objects have a large number of fields, or have several levels of nested objects, this pattern can be cumbersome. A simple way to do this is to stringify both objects, and then directly compare the strings:

componentWillReceiveProps(newProps) {
  if (JSON.stringify(this.props.prop1) === JSON.stringify(newProps.prop1)) {
    // Do stuff here
  }
}

This method generally safe to use, but is not guaranteed to work, since object ordering is not always guaranteed. It's important to test this out in your component to verify.

So now that I can safely modify and compare objects, is there a way for me to control when components render?

Yes! The best way to do this is to use the method shouldComponentUpdate().  shouldComponentUpdate() comes directly after componentWillReceiveProps() in the React lifecyle, and is also only called after the initial rendering. componentWillReceiveProps() allows you take action before a re-rendering, and shouldComponentUpdate() serves as a secondary intervention point, to either stop or allow the component to re-render. The same comparison techniques used for componentWillReceiveProps() can be used in shouldComponentUpdate().

Hope you found these React tips on comparing objects helpful! Check out some of our other posts to learn more about React.