Immutability
Immutability is a core principle in functional programming. In this post, I am going to list some of the best practices used to keep data structures immutable in JavaScript without using any third-party libraries.
Which data types are immutable in Javascript then ?
Primitives data types like string, number and boolean are immutable by default, they cannot be altered so you do not need to apply any "magic" to keep them immutable. We cannot say the same for objects and arrays which are mutable by default, because they are only references.
Following, We will go through some operations that will help us to keep also objects and arrays immutable.
Operations on arrays.
Cloning an array of primitive data types.
const sourceArray = [1,2,3];
const clonedArray = [...sourceArray];
// or you can do
const clonedArray = sourceArray.slice(0);
Cloning an array of objects which props are primitive data types.
const sourceArray = [{ a: 1}, { b: 2 }, { c: 3}];
const clonedArray = sourceArray.map(item => ({...item}));
Adding an new element into an array.
const sourceArray = [1,2,3];
const newArray = [...sourceArray, 4];
const sourceArray = [{ a: 1}, { b: 2 }, { c: 3}];
const newArray = [...sourceArray, { d: 4}];
Removing an element from an array.
const itemToRemove = 3;
const sourceArray = [1,2,3];
const newArray = sourceArray.filter(item => item !== itemToRemove);
Replacing an element into an array.
const itemToAdd = { id: 2, a: 4 };
const sourceArray = [{id: 1, a: 1}, {id: 2, a: 2}, {id: 3, a: 3}];
// replacing without caring about position
const newArray = [...sourceArray.filter(item => item.id !== itemToAdd.id), itemToAdd];
// replacing caring about position
const indexOldElement = sourceArray.findIndex(({ id }) => id == itemToAdd.id);
const newArray = Object.assign([...sourceArray], {[indexOldElement]: itemToAdd});
// or you can do
const newArray = [...sourceArray.slice(0, indexOldElement), itemToAdd, ...sourceArray.slice(indexOldElement + 1)]
Operations on Objects.
Adding a new prop.
const sourceObj = { a: 1, b: 2};
const newProp = { c: 3 };
const newObj = { ...sourceObj, ...newProp};
// or you can do
const c = 3;
const newObj = { ...sourceObj, c};
// newObj = { a: 1, b: 2, c: 3};
Removing a prop.
const sourceObj = { a: 1, b: 2, c: 3};
const { b, ...newObj } = sourceObj;
// console.log(newObj) => { a: 1, c: 3};
Update a nested Object which props are primitives.
const sourceObj = { a: 1, b: 2, c: { d: 3, e :4 } };
const c = { ...sourceObj.c, f: 5 }
const newObj = { ...sourceObj, c };
Update a nested Object which props are not primitives.
const sourceObj = { a: 1, b: 2, c: { d: [1, 2, 3 ], e :4 } };
const d = [ ...sourceObj.c.d, 4 ];
const c = { ...sourceObj.c, d }
const newObj = { ...sourceObj, c };
Unfortunately, the process of correctly applying immutable updates to nested object can easily become verbose and hard to read.
Few words on performances?
Creating a new object is more expensive in term of time and memory consuming, but these disadvantages are smaller then advantages in so many cases.
Advantages like being able to fast compare two immutable objects relying only on the identity/strict equality operator check oldObject === newObject
, or reducing the possibility of nasty bugs, which might happen, in applications that shares data structures across different components is something that you should think about before starting coding.
Conclusion
If you are currently using or learning any library based on immutability all the operation described above can help you. I hope you enjoyed reading this short post, feedbacks are welcome!
Top comments (0)