Immutability in React
JavaScript objects are mutable. BUT, React asks that we treat objects in state as immutable. Should we listen?
Photo by Simon Schwyter on Unsplash
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.