Mutability is the ability to change.
In Javascript, there are mutable and immutable data types. Strings, numbers, and booleans are immutable while arrays and objects are mutable.
Immutability
Data that is immutable cannot be changed. Immutability works by not changing the original data structure but rather by creating a copy of the original data structure. A new instance of the original data structure is created. For example a string value.
let name = "Juty"
//Let's change the first letter J to D
name[0] = "D"
console.log(name) // logs "Juty" on the console
This shows that you cannot change an individual character within the string. The only way to change the string would be to assign a new string to the variable.
let name = "Juty"
name="Duty"
console.log(name) // logs "Duty" onto the console
Another way would be to use manipulation methods like toUpperCase(), replace() or trim(). These methods return a new string and do not modify the original one.
const str = "miami"
const newStr = str.toUpperCase()
console.log(newStr) //logs "MIAMI" on the console
console.log(str) //logs "miami" on the console
The original string was not modified. It still remains to be "miami". The toUpperCase added to str did not cause modification of the original str, but rather created a new string which is rendered in uppercase.
Mutability
Data that is mutable can be changed. We can modify the original data structure. Arrays and Objects are mutable. We can add or remove elements in an array using array methods like push(), pop(), shift(), unshift(). These methods modify the original array.
//Original array
const numbers = [1, 2, 3]
numbers.push(4)
//New array
const myNumbers = numbers
myNumbers.push(89)
console.log(myNumbers) //Logs [1, 2, 3, 4, 89] on the console
console.log(numbers)//Logs [1, 2, 3, 4, 89] on the console
89 is not only pushed to myNumbers array but also pushed to numbers array. This is because objects and arrays are passed by reference and not by value. Therefore, mutating the new array also mutates the original array.
Examples of mutable array methods include: reverse(), shift(), unshift(), push(), pop(), splice(), sort()
//Original object
const person = {
name: "John",
age: 21,
eyeColor: "brown"
}
person.age=60
console.log(person) //logs {name:"John", age:60, eyeColor:"brown"}
person.age = 60 has modified the original object by changing age from 21 to the value 60. Mutability works by changing the original data structure.
Immutability in React
React uses some core concepts of functional programming and one of them is immutability. In ReactJs, data is immutable. This means that instead of changing the original data structure, we create a copy of that data structure and use it instead.
Reminder: Strings, numbers, and booleans are immutable. Objects and arrays are mutable
It is therefore important to keep the concept of immutability in mind when dealing with objects and arrays in React. You need to use methods that make them immutable.
Therefore, if you need to add an item to an array, using a push method in React does not work. This is because push() will mutate the original array and hence defy the rule of immutability.
We should instead use a method that will create a copy of that original array and modify the copy instead.
Some ways of achieving immutability in React are:
- Using a spread operator
- Using Object.assign(), Object.freeze()
- Using immutable array methods such as map(), filter(), concat(), slice()
Example
const person1 = {name: "John", age: 30}
const person2 ={...person1, eyeColor: "brown"}
console.log(person1) //logs {name : "John", age : 30}
console.log(person2) //logs {name : "John", age : 30, eyeColor : "brown"}
By using the spread operator, we copied the properties of person1 into a new object(person2). Notice that adding a new property (eyeColor:"brown") to person2 did not reflect in person1. That is because the spread operator allows us to create a copy of the original object. We therefore modify the copy and not the original object. Hence, the original object remains intact.
const numbers = [1, 2, 3, 4]
const myNumbers = [...numbers, 25]
console.log(numbers) //logs [1, 2, 3, 4]
console.log(myNumbers) //logs [1, 2, 3, 4, 25]
The original array was not mutated. It remained intact. What was mutated is the copy.
const lengths = [4, 2, 3]
const area = lengths.map(length=>length*length)
console.log(area) //logs [16, 4, 9]
console.log(lengths) //logs [4, 2, 3]
Immutability therefore requires you to use methods that do not mutate the original data structure. Instead of changing the original data structure, we create a copy of that data structure and mutate it instead.
Why is Immutability important?
With mutable data, change is done to every part of code that points to the same address in memory. If there is a bug in one part of the data, it creates bugs in the rest of the data that point to that address. This makes tracking bugs difficult and change is unpredictable. When one reference is being changed many times, we have no history of the changes. This is because assigning a new value to a variable rewrites the memory hence the previous value is lost. This means we will have no access to the previous state. Mutation only gives the current state and no previous state.
With immutability, we start operating on values and not on pointers to memory. Immutability is value oriented. With immutability, we create copies of the original data structure and work with them. Therefore the copied/changed structure represents a different value. The previous data structure and the changed one are therefore not the same. This generates a new version of the state which reflects the change.
This way, immutability helps to prevent bugs, enhances efficiency and predictability.