Immutability

Dive deep into the concept of immutability and why is it important in react

Mutable is something whose value can be changed.

And its opposite, immutable is something whose value cannot be changed.

Immutability is very big in the world of javascript. It is one of the core principles of javascript and other javascript-based libraries.

TLDR: The concept of immutability is that we don't do modifications to our original data. Whenever required we make a new copy of our data and perform updation on that copy of data instead of changing our original data. This way, we preserve our original data and serve the application with the required modifications.

Let's see what happens if we don't follow this.

let car = {
  name: "Tesla",
  modal: "Modal X",
}

const addRedColor = (carToBeColored) => {
  carToBeColored.color = "red"
  return carToBeColored
}

Here we have:

  • An object car, which doesn't have any color property.
  • To add color to the car we pass it to the addRedColor function which adds a color property to the car and returns it
  • Notice we are not making a copy of the carToBeColored object we are directly modifying its content
let car = {
  name: "Tesla",
  modal: "Modal X",
}

const addRedColor = (carToBeColored) => {
  carToBeColored.color = "red"
  return carToBeColored
}

let redCar = addRedColor(car)
console.log(redCar) //  { name: 'Tesla', modal: 'Modal X', color: 'red' } 

console.log('Are they the same?', car === redCar); // true
console.log(car) // { name: 'Tesla', modal: 'Modal X', color: 'red' }

Now we have a new redCar object which is getting its value from the addRedColor function. It is adding a color property to the car object. So now redCar has a color property that has a red value. This is what we wanted!

But the trouble begins here. Now, redColor has a color property because we added it but we didn't do that to the original car object which now also has a color property. This is not what we wanted. So what happened here?

In the above example, we declared a new variable redCar which is getting its value from the addRedColor function. If we notice that we passed the car object to the function and we are operating on the same object and returns that only. So what we are getting is the car object with color property, which is not a new object itself. Even though car and redCar are two different variables but they are pointing to the same reference in memory as that of the car object. To put it in simpler words, both variables are pointing to the same address in a memory location.

This means whatever changes we make to the newCar object would be reflected in the car object and vice versa because they are not two different objects in memory but the same object in memory. This is exactly what is happening here.

Now we can clearly see a major flow if we mutate the data. This could have serious consequences for an application. You would be pissed if you booked a black Tesla and got an orange Tesla instead.

How to work with immutable data.

const car = {
  name: "Tesla",
  modal: "Modal X",
}

const addRedColor = (carToBeColored) => ({
  ...carToBeColored,
  color :"red"
})

const redCar = addRedColor(car)

console.log('Are they the same?', car === redCar); // false
console.log(redCar) //  { name: 'Tesla', modal: 'Modal X', color: 'red' } 
console.log(car) // { name: 'Tesla', modal: 'Modal X' }

Here we are making a new copy using the spread operator and then making the required changes. So when we return the object we are actually returning a new object which would have a new reference in memory unlike before.

React Prefers Immutability

In the world of react, it goes big on immutability. It not only just prefers immutability but a lot of its efficient working depends on immutability. It’s important to never mutate state or props. Whether a component is a function or a class doesn’t matter for this rule.

Every application needs and uses a state for its functioning. You store data and update and display data through the state. Let's see an example.

import { useState } from "react";

export default function App() {
  const [team, setTeam] = useState({
    name: "Team A",
    score: 0
  });
  const goodChangeState = () => setTeam({ ...team, score: team.score + 1 });
  const badChangeState_1 = () => {
    const newState = team;
    newState.score = newState.score + 1;
    setTeam(newState);
  };
  const badChangeState_2 = () => (team.score = team.score + 1);
  return (
    <div className="App">
      <h1>Team : {team.name}</h1>
      <h1>Score : {team.score}</h1>
      <button onClick={goodChangeState}> Immutable State Change</button>
      <button onClick={badChangeState_1}> Same Reference State Change</button>
      <button onClick={badChangeState_2}> Mutable State Change</button>
    </div>
  );
}

Here we have

  • A state team consists of the team name and its score.
  • A function goodChangeState uses a setter function to update the state. We are making a new copy of the state and modifying it which is then set as a new state.
  • Another function badChangeState_1 declares a new variable, makes modification and uses the setter function to update the state
  • Then we have badChangeState_2 which directly updates the state.

Now go play with the demo.

So as you have noticed

  • Team's score is not updated when we update the state with Mutable State Change nor Same Reference State Change
  • Team's score works perfectly when we update the state with Immutable State Change

Before I explain what happened here, it's important to remember that:-

React re-renders when it detects a state change.

  • Immutable State Change (see goodChangeState function) works perfectly as we are using the setter function and supplying a new object to it. React hence detects a new state change and renders the component again and we get to see an updated value.
  • Same Reference State Change (see badChangeState_1 function) doesn't update the score. You might say that we are using the setter function and passing a new variable too but that doesn't work as expected. That's because the new variable (newState) has the same reference as the team (original variable). And when we make a change to it, the change does happen but it happens to the same original state, since it is not a new state react doesn't re-render.
  • Mutable State Change (see badChangeState_2 function) also didn't work because now you know it, we didn't pass a new state, just directly changed the state, and react doesn't re-render the component.

That's how you update the state, immutably. Now you know javascript and react a little better.