In this lesson, I use Expect Library to make test assertions, and deepFreeze to make sure that my code is free of mutations.
<!DOCTYPE html>
<head>
<meta charset='utf-8'>
<script src="https://wzrd.in/standalone/expect@latest"></script>
<script src="https://wzrd.in/standalone/deep-freeze@latest"></script>
</head>
<body></body>
</html>
Let's say that I want to implement a count release application. I would need to write a few functions that operate on its trait, and its trait is an array of JavaScript numbers representing the individual counters.
The first function I want to write is called Add Counter, and all it should do is to append a zero at the end of the past array.
const addCounter = (list) => {
list.push(0);
return list;
};
const testAddCounter = () => {
const listBefore = [];
const listAfter = [0];
expect(
addCounter(listBefore)
).toEqual(listAfter);
};
testAddCounter();
console.log('All tests passed!');
At first, I use the array push method to add a new item at the end of the array, and it works. However, we need to learn to avoid mutations in Redux, and I'm enforcing this by calling deepFreeze on the original array.
const addCounter = (list) => {
list.push(0);
return list;
};
const testAddCounter = () => {
const listBefore = [];
const listAfter = [0];
deepFreeze(listBefore);
expect(
addCounter(listBefore)
).toEqual(listAfter);
};
testAddCounter();
console.log('All tests passed');
Now my attempt to push does not work. It cannot add a new property to a frozen object. Instead of push, I'm going to use the concat method, which does not modify the original array.
const addCounter = (list) => {
return list.concat([0]);
};
const testAddCounter = () => {
const listBefore = [];
const listAfter = [0];
deepFreeze(listBefore);
expect(
addCounter(listBefore)
).toEqual(listAfter);
};
testAddCounter();
console.log('All tests passed');
Now the tests pass without mutations, and I can also use the new ES6 erase spread operator to write the same code in a more concise way.
const addCounter = (list) => {
return [...list, 0];
};
const testAddCounter = () => {
const listBefore = [];
const listAfter = [0];
deepFreeze(listBefore);
expect(
addCounter(listBefore)
).toEqual(listAfter);
};
testAddCounter();
testRemoveCounter();
console.log('All tests passed');
My next function is called removeCounter, and it accepts two arguments, an array of numbers, and the index of the number to skip from the array.
If I've got three numbers and I'm passing one as the second argument, I expect to receive an array with two numbers with the second item skipped in the result array.
const removeCounter = (list, index) => {
//splice method returns 1 value from position index
return list.splice(index, 1);
};
...
const testRemoveCounter = () => {
const listBefore = [0, 10, 20];
const listAfter = [0, 20];
expect(
removeCounter(listBefore, 1)
).toEqual(listAfter);
};
Usually, to delete an item from the array, I would use the splice method. However, splice is a mutation method, so you can't use it in Redux.
I'm going to deepFreeze the array object, and now I need to figure out a different way to remove an item from the array without mutating it.
I'm using a method called slice here, and it doesn't have anything to do with splice. It is not mutating, and it gives me a part of the array from some beginning to some end index.
What I'm doing is that I'm taking the parts before the index I want to skip and after the index I want to skip, and I concatenate them to get a new array.
...
const removeCounter = (list, index) => {
return list
.slice(0, index)
.concat(list.slice(index + 1);
};
...
Finally, instead of writing it as a method chain with concat calls, I can use the ES6 erase spread operator to write it more concisely.
...
const removeCounter = (list, index) => {
return [
...list.slice(0, index),
...list.slice(index + 1)
];
};
Now that we implemented adding and removing counters, let's implement increment in the counter. The increment counter function takes your arguments, the array and the index of the counter that should be incremented, so the return value has the same count of items, but one of them is incremented.
```javascript
const addCounter = (list) => {
return [...list, 0];
};
const removeCounter = (list, index) => {
return list
.slice(0, index)
.concat(list.slice(index + 1));
};
const incrementCounter = (list, index) => {
list[index]++ ;
return list;
};
const testAddCounter = () => {
const listBefore = [];
const listAfter = [0];
deepFreeze(listBefore);
expect(
addCounter(listBefore)
).toEqual(listAfter);
};
const testRemoveCounter = () => {
const listBefore = [0, 10, 20];
const listAfter = [0, 20];
deepFreeze(listBefore);
expect(
removeCounter(listBefore, 1)
).toEqual(listAfter);
};
const testIncrementCounter = () => {
const listBefore = [0, 10, 20];
const listAfter = [0, 11, 20];
expect(
incrementCounter(listBefore, 1)
).toEqual(listAfter);
};
testAddCounter();
testRemoveCounter();
testIncrementCounter();
console.log('All tests passed!');
Directly setting the array value at index works, but this is a mutation. If we add a deepFreeze call, it's not going to work anymore, so how do we replace a single value in the array without mutating it?
const testIncrementCounter = () => {
const listBefore = [0, 10, 20];
const listAfter = [0, 11, 20];
deepFreeze(listBefore);
expect(
incrementCounter(listBefore, 1)
).toEqual(listAfter);
};
It turns out that the answer is really similar to how we remove an item. We want to take the slice before the index, concat it with a single item array with a new value, and then concat it with the rest of the original array.
const incrementCounter = (list, index) => {
return list
.slice(0, index)
.concat(list[index] + 1)
.concat(list.slice(index + 1)
};
Finally, with the ES6 spread operator, we can spread over the left part of the array, specify the new item, and then spread over the right part of the original array, and this looks much nicer.
const incrementCounter = (list, index) => {
return [
...list.slice(0, index),
list[index] + 1,
...list.slice(index + 1)
];
};
In this lesson, you learned how to use the concat method or the spread operator, and the slice method to add, remove, and change items in arrays without mutating them, and how to protect yourself with deepFreeze from mutation in your tests.