Immutability and React State for Beginners

Recently, we have been expanding. This has involved training some of our more technical support technicians, and taking on some Co-Op students and introducing them to the world of React and JavaScript. I have welcomed the additional help to my overflowing plate of outstanding projects. Ramp-up, however, takes it’s time. So I find that in the hurry to get them up to speed some key concepts are missed.¬†As well, it reminded me of my own misunderstandings of JavaScript when I first started learning.

One of the main concepts to learn is how JavaScript uses references with its various types. This is key to understanding how to properly adhere to one of React’s most fundamental tenets. Don’t mess with state! State should be considered immutable.

In this post I am going to go through a few of the concepts that can be struggling when becoming a fledgling Reactolyte. Most specifically:

  1. Immutability and React State
  2. JavaScript references
  3. Techniques for modifying state properly

 

Immutability and React State

I am going to assume that you are not totally fresh of the boat to React development. So you should be at least partially aware of what state, props and components are and how to use them. If you want a great overview to understanding all the pieces together, I highly recommend having a close look at Thinking in React.

State, simply put, is the store of variables within a component that holds the changing values that dictate the view. This could be the index for a selected tab, a boolean to indicate if a field is visible, the actual values that a user has entered into a form. Basically, anything that changes. If it doesn’t change, and just sets the stage for the component’s configuration, or if it is controlled by a parent component, then just use props.

Immutability is the concept that an object or value can not, or should not change. In the React world, it is best practice that the state of a component remain immutable. This may seem like a contradiction. How can something that is supposed to change depending on user interaction, or other actions be immutable, or unchanging?

The key to understanding this best practice is to understand that React is a Framework. It is doing a lot of magic under the hood. So by you mucking around with state directly, when you don’t know what you are doing, you’re basically pouring motor oil all over the engine and calling yourself a mechanic.

 

So what’s the big deal?

The problem is that because React is a Framework, it has life-cycle methods, optimizations, and a whole host of other complexities that do all the heavy lifting. In particular, you should be aware of and using the following:

setState

If you want to update state, you should be using setState. This is the entry point into telling React that the state should change, and to pass in the new values for it. This will kick off the lifecycle methods that the framework requires, and ultimately the execution of the render with the updated state to reflect the new changes.

 

shouldComponentUpdate(nextProps, nextState)

One of the main reasons you never want to modify the state directly is because of this method. This is where you can potentially short circuit the lifecycle methods and prevent a render from occurring. At the time it is called, the current state is still available through this.state. Unless of course, you were a dummy and modified it directly. Then any logic that was depending on knowing the current state, and checking if the component should actually update are lost. This is where you may lose a tonne of time debugging if you are not aware of what is going on.

 

componentWillUpdate(nextProps, nextState)

If the component is indeed on the path to eventual update (aka render). Then here is the spot you can do additional prep work based on the nextState and still have access to the previous state, again via this.state. All bets are off if you modified it directly.

 

componentDidUpdate(prevProps, prevState)

This one follow the similar pattern as the others. However, in this case the component has already updated, and the DOM changed to reflect the view based on the new state. Here prevState gives you access to what used to be in state. If this was modified directly outside of this.setState then again, your component will be buggy here for any logic dependent on that.

 

But I’m NOT using those methods. So again, what’s the big deal?

This is where things start to get pedantic. If you have been using React for a while, and know when and how all of the lifecycle methods are used and when to use them. Then you may do shortcuts. I mean you can do anything you want really.

When you are learning React, this becomes an important principal and best practice to stand by. Because it will protect you from falling into many bug laden traps that arise from improper use. Even if your components don’t start out using the aforementioned methods, inevitably complexities may arise during development that necessitate their unplanned use at a later time. As things evolve we may introduce bugs if we don’t stick to core principles.

So after giving this rant I hope you are now thoroughly convinced to never modify state directly. The catch now is, of course, to implement this properly. After reviewing some dubious pull requests I realised that some other key concepts are needed to fully understand this best practice and how to properly code it. For that we need to look at some of the finer points of JavaScript.

 

JavaScript references

Asking a beginner to not modify state directly may yield some interesting results.

The first one is very obviously wrong. It modifies state directly and calls setState with no parameters, which ends up calling render with what should be the previous state. Mind you it does manage to work since state was modified directly, but React will complain loudly in the developer console that you are being a dolt.

The second one above looks like it does the trick. A new variable is declared to store the value from state before it is updated and seemingly set properly in this.setState.

Here’s the catch: these are both bad. And here’s why…

JavaScript assigns and passes only primitive types by value. Objects and arrays are assigned and passed by reference. This is a basic concept in most programming languages. There are differing rules around reference vs value types as well as how to deep copy objects in these languages. So knowing how to do this in JavaScript is very important.

 

Techniques for modifying state properly

Or rather, techniques for copying objects and arrays to new variables properly. This way you are working with new data in memory, and not just updating the same object via reference. This will allow you to properly set the state with the new object once modified with no side effect.

slice(begin, end) OR ES6 array spread

Slice is, and has been, one of the most valuable tools in the JavaScript developers tool belt. It’s default function is to take an array and return a slice of it starting from the begin index all the way to the end index. It will return a new shallow copy of that array. The other option is the ES6 array spread operator. Which turns and array into its individual values. Add [] around it, and you have a new array.

By just adding the single argument 0, we basically just slice the entire array from beginning to end and return a new in memory array. Then we are free to modify at will without affecting the original state. I personally like the syntax of the new ES6 array spread. Just be aware of what browser you are targeting as it is not available in IE. Or be sure to use a transpiler like Babel with the appropriate settings.

NOTE: There is one very key problem with this, it will only do a shallow copy, meaning if you have an array of objects, you are just back in the same place. Slice will create a new array with references to the objects in the original array. To do this properly we need something a bit more complex.

The reality is that there is no simple default method that will do this in pure JavaScript. You could iterate over the objects in an array and push to a new one using something like Object.assign(). However, that too will only clone 1 level deep. If an object in the array has its own child objects, then those two would be passed be reference.

If you had access to a library like lodash, then you can access built-in methods for deep cloning and use something like _.map(myArray, _deepClone). But there is actually a simple way without having to add an entire library.

 

Deep clone for Arrays or Objects

The quickest and most reliable way to deep clone an object is to serialize and deserialize it using the built in JSON functions stringify and parse.

The great thing is that this works for both arrays and objects, or arrays of objects however many levels deep. This will work for almost all cases for state for a React component.

 

Conclusion

React works best when we use best practices. One of the key recommendations is to keep state immutable. In order to do this properly we need to know how JavaScript reference and value types work. When we do not do this properly, or modify state directly, we interfere with the intended function of the lifecyle methods of React and may introduce bugs.

Knowing the type of object that we wish to modify, we can use the appropriate method to clone it. For arrays of simple value types we can use Array.slice or ES6’s array spread operator. For more complex types, and deeply nested objects we can use the JSON functions stringify and parse to serialize and deserialize into a new deep copy.

Leave a Reply

Your email address will not be published. Required fields are marked *