React is a famous library for building dynamic user interfaces. Data immutability is an idea that came from functional programming and applying it to design of your front end app can have many benefits.
What is Data mutation?
To understand the idea of data mutation, will need to step back to the foundations of programming and understand how we can compare data structures in javascript. Comparing primitive types is pretty easy.
let num1 = 10,
num2 = 10;
num1 === num2; // true
It is different in case of non-primitive data types.
let obj1 = { name: "Pragati", lname: "Garud" },
obj2 = { name: "Pragati", lname: "Garud" };
obj1 === obj2; // false
But they look same. Why are they not equal? 🙃
For above objects, they are same in terms of keys and values but as they are initialized separately, they are not same in terms of reference. The result would have been different if they would have been pointing to same address.
let obj1 = { name: "Pragati", lname: "Garud" },
obj2 = obj1;
obj1 === obj2; // true
This has one consequence though.
obj1.name = "unknown";
console.log(obj1.name); // unknown
console.log(obj2.name); // unknown
In above snippet, we have assigned unknown
to name
key of obj1
. But as obj1
and obj2
are pointing to same reference, change is visible in both variables. Every complex data structure(array/object) in javascript follows the same principles of reference equality.
In short, For non-primitive cases ==
and ===
operators compare variables by their reference. we can compare them by values as well, but in a complex data structure, this will need multiple equality checks which are called deep equality checks.
let obj1 = { name: "Pragati", lname: "Garud" },
obj2 = { name: "Pragati", lname: "Garud" };
valueCompare(obj1, obj2); //true
Implementing such equality would be harder with nested data structures.
// Input: obj1 and obj2
// Output: true if obj1 is equal to obj2 in terms of values
function valueCompare(obj1, obj2) {
const keys1 = list of keys of obj1;
const keys2 = list of keys of obj2;
if (keys1.lenth !== keys2.length) return false;
for each key in keys1:
if (key is not present in keys2)
return false;
if (typeOf(keys1[key] != typeOf(keys2[key]))
return false;
if (keys1[key] is a primitive && keys1[key] !== keys2[key])
return false;
if (obj1[key] is non primitive):
isEqual = valueCompare(obj1[key], obj2[key]);
if(!isEqual) return false;
}
Get Rid of Mutations:
Just don’t mutate objects, instead, create a changed copy of it.
You can achieve that using following ways:
1] Object.assign: You can use Object.assign
function to create a copy.
let obj3 = Object.assign({}, obj1, { name: "unknown" });
obj3 === obj1; //false
In this way, every time you mutate an object, it gets a new reference.
2] [].concat(): You can use [].concat()
for changing array values in an immutable way.
let numbers = [10, 20, 30];
numbers[1] = 22; // This will not change reference to an array
// Instead what you can do copy list and perform update as below
let numbers = [10, 20, 30];
let changedNumbers = [].concat(numbers);
changedNumbers[1] = 22;
changedNumbers === numbers; // false
What it has to do with React?
React components have state, When you call setState function, component re-renders. React can’t assume anything about state, you can mutate however if you want. That’s why setting state always re-renders component, even if state value is not changed.
It’s hard to check whether your state has changed or not if your state is complicated (contains nested object). You need to make deep equality check because when objects are compared using reference equality, you can’t be sure whether next state is changed, and deep equality check will take more time as compared to re-render time. So React prefers to re-render component on every setState call instead of state change comparison.
Summary
We hope this blog has helped you understand how you could get rid of data mutations to avoid time-consuming deep equality checks and cut down those re-render times in React.