Pure javascript immutable arrays
About a year ago, I started developping an app in React. I chose React on a whim because I wanted to learn a new framework and was not fond of the java angular way of doing things. I learned about immutability shortly after but did not pay so much attention to it. In my mind, the state of a React component was kind of immutable[1] and that was enough for me. Boy did I change my mind.
Spend a day fighting a bug that was caused by mutability and you could change your mind too. Unfortunately, I still don't use immutability everywhere but my new code is using it, a lot!
But why wouldn't I use a library like ImmutableJS or the update addong of React and just be done with it you ask? Well, I could and in fact, I am in some places, but if immutability can be achieved just from vanilla javascript, it's at least good to know. You could need it just in one place and don't want to bother getting a library just for that.
I'll start by talking about arrays because ES2015 is just adding syntaxic sugar on top on existing functions.
Firstly, I went to see every basic function of the Array prototype and immutability will rely on these functions: concat, filter, map, reduce, reduceRight and reduceRight. These functions don't mutate the array but return a copy or a new array.
The mutative functions are push, pop, shift, unshift, sort, reverse, splice and delete (kind of). You can click on each one to see an immutable equivalent. The other functions just test or go through the array without doing anything to it.
Disclaimer: I voluntarily forgot the new ES2015 functions (fill and copyWithin) for simplicity and because I have no idea how to use these functions for the moment.
Push
// ES2015
function immutablePush(arr, newEntry){
return [ ...arr, newEntry ]
}
// ES5
function immutablePush(arr, newEntry){
return [].concat(arr, newEntry)
}
Pop
function immutablePop(arr){
return arr.slice(0, -1)
}
Shift
function immutableShift(arr){
return arr.slice(1)
}
Unshift
// ES2015
function immutableUnshift(arr, newEntry){
return [ newEntry, ...arr ]
}
// ES5
function immutableUnshift(arr, newEntry){
return [].concat(newEntry, arr)
}
Sort
This one is a bit of a downer because the Array.sort function will mutate your array and I don't think it will be useful to recode it. So the simplest way of getting a new sorted array is to make a copy, then sort it.
// ES2015
function immutableSort(arr, compareFunction) {
return [ ...arr ].sort(compareFunction)
}
// ES5
function immutableSort(arr, compareFunction) {
return arr.concat().sort(compareFunction)
}
Reverse
Array.reverse has the same problem than Array.sort, it's not practical to rewrite the function and just simpler to make a copy of the array before reversing it.
// ES2015
function immutableReverse(arr) {
return [ ...arr ].reverse()
}
// ES5
function immutableReverse(arr) {
return arr.concat().reverse()
}
Splice
// ES2015
function immutableSplice(arr, start, deleteCount, ...items) {
return [ ...arr.slice(0, start), ...items, ...arr.slice(start + deleteCount) ]
}
// ES5
function immutableSplice(arr, start, deleteCount) {
var _len = arguments.length
var items = Array(_len > 3 ? _len - 3 : 0)
for (var _key = 3; _key < _len; _key++) {
items[_key - 3] = arguments[_key];
}
return [].concat(arr.slice(0, start), items, arr.slice(start + deleteCount))
}
Delete
function immutableDelete (arr, index) {
return arr.slice(0,index).concat(arr.slice(index+1))
}
Copy an array
Maybe you just want to copy an array. This is the way. It could be useful if you need the return value of the previous mutable functions. Just make a copy and then use your function.
// ES2015
var arr = ['a', 'b', 'c']
var newArr = [ ...arr ]
// ES5
var arr = ['a', 'b', 'c'];
var newArr = [].concat(arr);
[1]: I thought that this.setState was the only way of updating the state but you can in fact mutate the objects in this.state and have the state change (without triggering an update of the component and watch everything go wrong on the next update).
Cover image credits : Rennet Stowe