Immutability in React

JavaScript objects are mutable. BUT, React asks that we treat objects in state as immutable. Should we listen?

Let's dive into the world of React and unravel the following mystery: why updating state feels like we're constantly moving into a new home instead of just rearranging our old furniture. 🏠

In this post, I will focus on two things:

βœ… What it means to use immutable objects in React

βœ… How to safely update objects in state

To mutate or not to mutate, that is the question!


Updating state in React: an example

Whether a beginner or a more seasoned React developer, code like the one below is relatively easy to grasp.

function SingleDie() {

    // set up the state: die is an object, with one property
    const [die, setDie] = useState({
        value: Math.ceil(Math.random() * 6),
        color: "blue"
    });

    // Generate new value when rolled, then update state
    function goodRollDie() {
        const newValue = Math.ceil(Math.random() * 6);
        setDie(oldDie => ({ ...oldDie, 
                            value: newValue 
        }));
    }

    // JSX code that renders the component
    return (
        <div>
        <p>Value: {die.value}</p>
        <button onClick={rollDie}>Roll Die</button>
        </div>
    );
}

Here we have a component named SingleDie that represents a single die which can be rolled to produce a random value between 1 and 6.

The state die is an object with two properties: value and color :

value: Gets a random number between 1 and 6

color: Set to the string "blue".

When the goodRollDie function is invoked a new random value between 1 and 6 is generated. The setDie function is called to update the die state with this new value. Here, it uses the current state (referred to as oldDie) to spread out all of its properties and only change the value property to the new random number. The color property remains unchanged.

Bad, Bad Code πŸ‘Ž

For me, a React newbie, the updating-state-by-creating-a-new-object approach was puzzling in the beginning. Why not just change the properties of the existing die object and update state with the modified object?! πŸ€”

function badRollDie() {
    // update value with a newly generated one
    die.value = Math.ceil(Math.random() * 6);  
    // set the modified object as the state
    setDie(die); 
}

It turns out that this approach is problematic because it directly modifies the state, which goes against the principle of immutability in React.

Just a reminder: JavaScript/React objects ARE mutable. We CAN change their content. BUT, React wants us (ok, implores us!) that we treat objects that are already in state as read-only, β€œas if they were immutable.”

To further quote from the React documentation, " . . . without using the state setting function, React has no idea that the object has changed. So React does not do anything in response. It’s like trying to change the order after you’ve already eaten the meal. While mutating state can work in some cases, we don’t recommend it. You should treat the state value you have access to in a render as read-only."

In the (BAD) code example above, the badRollDie function is designed poorly because it directly modifies the value property of the existing die object. 🚫

Instead, as the goodRollDie function shows, and I’m quoting again from the React documentation, "when you want to update an object, you need to create a new one (or make a copy of an existing one), and then set the state to use that copy." βœ”οΈ

The GOOD CODE πŸ‘

So what happens in the good code ? In the goodRollDie function, a new object is created using the spread operator when updating the die state. This technique ensures that the state is updated in an immutable way. βœ”οΈ

We generate a new value. Then, the spread operator creates a new object by copying all the properties of the oldDie object and overriding the value property with the newValue. This new object is used as the updated state to set the die using setDie. βœ”οΈ

function goodRollDie() {
  const newValue = Math.ceil(Math.random() * 6);

  // Copy the old fields &override the value property  
  setDie(oldDie => ({...oldDie, value: newValue}));
}

To summarize, a new object is created for the die to be updated. The . . . die syntax, using the spread operator, ensures that the properties of the existing die are copied into a new object. Then, for the goodRollDie function, a new value is created, which will override the previous value in the NEW object. All is good with the world now! 😊

The BIG QUESTION

Why immutability? As mentioned above, immutable data structures allow React to quickly determine if changes have occurred, and that makes the re-rendering process more efficient.

It turns out it provides other benefits as well, such as easier debugging, simpler code maintenance, and compatibility with future React features. React team's reasoning behind these benefits is more thorough than my one-sentence attempt, even if esoteric at times for a React beginner. So for right now, I will just have to take their word for it and simply follow the rule! 😎

My hope is that, as I continue to work in React and my coding skills improve, by embracing immutability (of course!) I will develop a deeper understanding of its advantages and how it benefits my projects.

In the meantime, I will remember to REPLACE, and NOT mutate. βœ”οΈ

Resources

React Documentation on Updating Objects In State

Blog post originally published on corinamurg.dev.

Β