Mutable Data and shouldComponentUpdate

So I'm working in a fluxy React app and I write a shouldComponentUpdate function that should be working, yet it always returns false. Why??

Click here for a CodePen that shows the problem.

This has happened to me three times now, so I'm writing about it to help anyone who may be having a similar issue, or at least, to warn those who don't tread carefully with mutable data.

Long story short, if you have nested data in your store, and you change a value there, you have now changed that value for any component that has a reference to it. If you're sending your data down from the store through props, you likely just ruined a shouldComponentUpdate function. Lets take a closer look.

I have an object as my store's state, and it looks like this:

{
  counter: {
    clicks: 0
  }
}

Now, I have a React component that ticks up clicks when clicked, and displays the count. On that component, I add a shouldComponentUpdate that looks like this:

shouldComponentUpdate: function(nextProps, nextState) {  
  return this.state.counter.clicks !== nextState.counter.clicks
}

Assuming that this component sets the store's state to its own whenever the store updates, this will not work. The shouldComponentUpdate function will always return false. That is because this.state.counter is a reference, and that reference points to the same object in memory as the object in the store. Thus, when the store's counter.clicks increments, the same value is seen by the component. By the time shouldComponentUpdate runs, this.state.counter and nextState.counter are the same object.

You can solve this by using Object.assign, and making the first parameter a new, empty object before modifying data, or by using something like Immutable.js.

I'm not against mutability or native JS objects or anything, but if you're working in React, and with a Flux-like data flow (Redux, Reflux, etc), you need to be sure you aren't modifying nested data in your store.

So if you have a shouldComponentUpdate function that's not working, it may be because you are modifying your app's data the wrong way.