1. 9
    Redux: Avoiding Array Mutations with concat(), slice(), and ...spread
    3m 54s

Redux: Avoiding Array Mutations with concat(), slice(), and ...spread

Dan Abramov
InstructorDan Abramov

Share this video with your friends

Send Tweet
Published 6 years ago
Updated a year ago

Learn how to avoid mutating arrays using concat(), slice(), and the ES6 array spread operator.

[00:00] In this lesson, I use Expect Library to make test assertions, and deepFreeze to make sure that my code is free of mutations.

[00:09] 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.

[00:23] 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.

[00:33] 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.

[00:49] 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.

[01:03] 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.

[01:15] My next function is called Remove Counter, and it accepts two arguments, an array of numbers, and the index of the number to skip from the array.

[01:25] 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.

[01:36] 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.

[01:46] 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.

[01:56] 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.

[02:08] 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.

[02:20] 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.

[02:33] 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.

[02:54] 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?

[03:10] 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.

[03:25] 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.

[03:38] 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.

Sequoia McDowell
Sequoia McDowell
~ 6 years ago

Is there a library of non-mutative array methods? Rewriting versions of Array.prototype.splice over and over to avoid mutation seems a bit crazy. :p

Dan Abramov
Dan Abramovinstructor
~ 6 years ago

Check out https://github.com/kolodny/immutability-helper.

Christian
Christian
~ 4 years ago

Avoiding mutations is made way easier with Immutable JS:

const addCounter = list => {
  return list.push(0);
};

const removeCounter = (list, index) => {
  return list.remove(index);
};

const incrementCounter = (list, index) => {
  return list.update(index, i => i + 1);
};

const testAddCounter = () => {
  const listBefore = List();
  const listAfter = List([0]);
  expect(addCounter(listBefore)).toEqual(listAfter);
  expect(listBefore.size).toEqual(0);
};

const testRemoveCounter = () => {
  const listBefore = List([0, 10, 20]);
  const listAfter = List([0, 20]);
  expect(removeCounter(listBefore, 1)).toEqual(listAfter);
};

const testIncrementCounter = () => {
  const listBefore = List([0, 1, 2]);
  const listAfter = List([0, 1, 3]);
  expect(incrementCounter(listBefore, 2)).toEqual(listAfter);
};

testAddCounter();
testRemoveCounter();
testIncrementCounter();
console.info("all tests passed");
Charles Owen
Charles Owen
~ 4 years ago

What are the performance tradeoffs for ensuring the immutability of an array with a large object graph, with potentially thousands of objects with nested objects within them? I'm assuming returning a new array builds an entirely new copy in memory or is it using pointers to the source array?

Team Authoring Learning Objects
Team Authoring Learning Objects
~ 4 years ago

Just taking this course now; I get a deepFreeze is undefined error. However, I noticed Object.freeze is built in and gets the error I want.

Alex Okros
Alex Okros
~ 4 years ago

Just taking this course now; I get a deepFreeze is undefined error. However, I noticed Object.freeze is built in and gets the error I want.

Just remember that if you need to make an object immutable, recursively freeze each property which is of type object (deep freeze). Otherwise you'll have a shallow freeze.