Why Use Immutable Syntax When Updating React State?
February 15, 2023
In React, updating state is a common task, but do you know that you should use immutable methods to update state? Why and how should you do it? What is considered an immutable approach? These are frequently asked questions in React interviews, as it is a common problem that developers encounter in their daily work. Let's explore the answers to these questions in this article.
What Is Immutable?
Immutable means something that cannot be changed, whereas mutable refers to something that can be changed. In programming, an object is mutable if its properties can be modified after it is created. On the other hand, an object is immutable if it is read-only and cannot be changed.
In the context of React, when we want to change a state, we should use an immutable approach. This means that we should not directly modify the state using a mutable approach. For example, if we have a state that represents a coordinate position, we should not modify it like this:
const [pointerPosition, setPointerPosition] = useState({ x: 0, y: 0 });
// In React, we should not do this
pointerPosition.x = 5;
Instead, we should use setState
to modify the state in an immutable way. For example:
onPointerMove={e => {
setPointerPosition({
x: e.clientX,
y: e.clientY
});
}}
Why Do We Need To Use Immutable in React?
The reason why we cannot directly modify an object in React and instead should use setState
is because objects are passed by reference. This means that when we modify an object directly, its memory location remains the same. If the object's memory location does not change, React will not be aware of the change and will not use the new value to re-render the UI. This can lead to unexpected results in the UI.
Follow-Up Question: How Can We Achieve Immutability in React?
In React, we always pass a new value to setState
to change the state. This may seem straightforward, as seen in the setPointerPosition
example, where we simply pass the new mouse cursor position. However, what if we want to keep some values in the current object? For example, in a login form, we want to keep the username the user has entered when they enter their password. How can we achieve this using an immutable approach?
const [loginInfo, setLoginInfo] = useState({
account: "",
password: "",
});
To create a new object while retaining some values from the original object, we can use the spread operator in JavaScript, which is denoted by ...
. For example, in the login form above, we can do the following:
setLoginInfo({
...loginInfo, // Use spread operator to copy values from the original object
password: e.target.value, // Write the values to be overwritten last
});
However, it is easy to forget to use this syntax in practice. In the community, there are some helper tools that can make it easier to write immutable code. For example, Immer is a popular tool used by React and Redux, which can help us write immutable code more easily (Editor's note: It is recommended to mention tools like Immer in an interview to demonstrate your knowledge and experience with such tools).
Follow-Up Question: Which JavaScript Array Methods Are Immutable?
In JavaScript, arrays are also objects, and in React, if we have an array as a state, we also need to use an immutable approach when updating it. Which JavaScript array methods are immutable?
Adding Elements to an Array
If we want to add a new element to an array and create a new array at the same time, we can use the spread operator ...
or the concat
method. While many people would intuitively think of push
and unshift
to add elements to an array in JavaScript, these two methods directly modify the array, so they should be avoided when updating an array state in React.
To insert an element into an array, we can use the slice
method, which allows us to create a new array. We can first slice the array from the start up to the index where we want to insert the new element, then add the new element, and finally slice the remaining part of the array. Because slice
returns a new array, it is an immutable operation.
const insertAt = 3; // The index where we want to insert the new element (3 is just an example)
const newArray = [
...array.slice(0, insertAt),
{ newItem },
...array.slice(insertAt),
];
Removing Elements From an Array
If we want to remove elements from an array and create a new array at the same time, we can use the filter
method, which removes elements that do not meet the specified conditions and returns a new array. While pop
and shift
are often used to remove elements from an array in JavaScript, they directly modify the array, so they should be avoided when updating an array state in React.
Modifying the Value of an Array
If we want to modify the value of an element in an array, we can use the assignment operator
array[index] = "new value";
to directly modify the value. However, we cannot do this in React, as it would modify the original array directly and thus violate the immutable principle. Instead, we can use the map
method to create a new array and change the value of the element we want to modify.
const modifiedArray = array.map((item, idx) => {
if (idx === index) {
// Modify the value of the element
return ...
} else {
// Return the original element
return item;
}
});
Using the methods mentioned above can help us maintain immutability when working with arrays in React.