Never Mutate State - what is the proper way to clone a state?

Tell us what’s happening:
The code below passes the test but I m looking for the correct way to ‘clone’ a state. The state may not necessary be an array. It could be a more complicated type such as an object with various types. The lesson claims javascript provides a few way to make state immutable easy. I did a search on cloning a javascript variable and can not find an easy way to do so (regardless of type). Can someone provide a hint? What should I search for?

Your code so far


const ADD_TO_DO = 'ADD_TO_DO';

// A list of strings representing tasks to do:
const todos = [
  'Go to the store',
  'Clean the house',
  'Cook dinner',
  'Learn to code',
];

const immutableReducer = (state = todos, action) => {
  switch(action.type) {
    case ADD_TO_DO:
      // don't mutate state here or the tests will fail
      let newState = [];
      state.forEach(
          e=>newState.push(e)
      );
      newState.push(action.todo);
      return newState;
    default:
      return state;
  }
};

// an example todo argument would be 'Learn React',
const addToDo = (todo) => {
  return {
    type: ADD_TO_DO,
    todo
  }
}

const store = Redux.createStore(immutableReducer);

Your browser information:

User Agent is: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0.

Link to the challenge:

You can use the spread operator. This is valid for arrays and objects.

Yes, as mentioned, the modern way to do it is with the spread operator:

const arr1 = [1, 2, 3];
const arr2 = [...arr1];
console.log(arr2);
// [1, 2, 3]
const arr3 = [...arr1, 4, 5];
console.log(arr3);
// [1, 2, 3, 4, 5]

This creates a completely new copy of that array/object. Pre ES6 you could use the slice method, but spread is the way to go now.

There is the spread syntax as mentioned, but this won’t help much with nested objects. It’s always a shallow clone. This isn’t a JS-only issue: if you have a complex data structure and you want make immutable updates, and do it efficiently, that’s a hard problem. You can deep clone it, but that’s slow (it generally needs to recursively walk the structure).

YMMV, but most robust solutions in actual apps that need this functionality will generally involve an immutability helper library:

  • Immutable is very widely used. It’s a complete set of immutable collection structures, so you need to convert to and from them. Once you’ve got your data in a structure, it’s fairly easy to use, but that step to or from can be a pain
  • Mori is much less widely used, similar to Immutable, has a nice API.
  • Immer is getting more popular, it ridiculously easy to use. It has no special data structures, just a single function which takes an object (your state) + a callback function. In the callback function, you can do anything you want (mutably) to a draft version of the state. The return value is that draft merged with a copy of the original state.

Edit: IMO immer is the simplest solution: here’s what your code would look like (note I would not advise adding libraries for something this simple unless you need to update things inside specific todos):

const immutableReducer = (state = todos, action) => {
  return produce(state, (draftState) => {
    switch(action.type) {
      case ADD_TO_DO:
        draftState.push(action.todo);
      default:
    }
  };
};
  • No return in the switch case, produce is already returning the value
  • Default left empty: produce returns the existing state by default. It’s only there to stop the linter screaming, it isn’t necessary.

A shallow copy of state is all redux needs. As long as you’re always treating state as immutable in your reducers, any selectors that operate deeper within state will also keep the state pure when you compose them. You certainly don’t need Immutable.js or its relatives to get started with redux.

But they are nifty in their own right :slight_smile:

I agree with both Dan and Chuck. Dan is right that spread operators only do shallow clones and something extra is needed if you want a deep clone. And I agree with Chuck that in Redux a shallow clone is usually what you want. Redux says as much in its docs.

Ok I am going to keep that in mind, thanks.

Kevins answer helped a lot.
The code that worked for me:

const ADD_TO_DO = 'ADD_TO_DO';

// A list of strings representing tasks to do:
const todos = [
  'Go to the store',
  'Clean the house',
  'Cook dinner',
  'Learn to code',
];

const immutableReducer = (state = todos, action) => {
  switch(action.type) {
    case ADD_TO_DO:
        return addItemToList(state,action.todo);
      // don't mutate state here or the tests will fail
    default:
      return state;
  }
};

let addItemToList = (list,item) => {
    let ret = [...list];
    ret.push(item);
    return ret;
}

// an example todo argument would be 'Learn React',
const addToDo = (todo) => {
  return {
    type: ADD_TO_DO,
    todo: todo
  }
}

const store = Redux.createStore(immutableReducer);