Redux: Never Mutate State

  • Today FreeCodeCamp will be giving us a lesson about enforcing the key [principle of state immutability in Redux. Which means that you never modify state directly. Instead you return a new copy of state.
  • Redux does not actively enforce state immutability in its store or reducers. Remember strings and numbers are primitive values and are immutable by nature. In other words, 3 is always 3. You cannot change the value of the number 3. An array or object, however, is mutable.
  • Code:
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
      return
    default:
      return state;
  }
};

const addToDo = (todo) => {
  return {
    type: ADD_TO_DO,
    todo
  }
}

const store = Redux.createStore(immutableReducer);
  • Above we have a store and reducer in the code editor for managing to-do items. You have to finish writing the ADD_TO_DO case in the reducer to append a new-to-do to the state. You have tp find a way to return a new array with the item from action.todo appended to the end.

  • Answer:

const ADD_TO_DO = 'ADD_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:
      let newList = [...todos]
      newList.push(action.todo)
      return newList;
    default:
      return state;
  }
};

const addToDo = (todo) => {
  return {
    type: ADD_TO_DO,
    todo
  }
}

const store = Redux.createStore(immutableReducer);

store.dispatch(addToDo('PS5 Time'))****
console.log(store.getState()) // 
[ 'Go to the store',
  'Clean the house',
  'Cook dinner',
  'Learn to code',
  'PS5 Time' ]


console.log(todos) // 
[ 'Go to the store',
  'Clean the house',
  'Cook dinner',
  'Learn to code' ]

Use the Spread Operator on Arrays

  • (ES6) To help enforce state immutability in Redux is the spread operator: .... which I used in the lesson above which produces a new array from an existing array.
  • To clone an array but add additional values in the new array, you could write [...myArray, 'new value']. This would return a new array composed of the values in myArray and the string new value as the last value.
  • It's important to know that it only makes a shallow copy of the array. It only provides immutable array operations for one-dimensional arrays.

  • Code:

const immutableReducer = (state = ['Do not mutate state!'], action) => {
  switch(action.type) {
    case 'ADD_TO_DO':
      // Don't mutate state here or the tests will fail
      return
    default:
      return state;
  }
};

const addToDo = (todo) => {
  return {
    type: 'ADD_TO_DO',
    todo
  }
}

const store = Redux.createStore(immutableReducer);
  • Answer:
const immutableReducer = (state = ['Do not mutate state!'], action) => {
  switch(action.type) {
    case 'ADD_TO_DO':
      let newArray = [...state, action.todo]
      return newArray
    default:
      return state;
  }
};

Remove an Item from an Array

  • Now we gotta practice removing items from an array. Other useful JavaScript methods include slice() and concat().

  • The reducer and action creator were modified to remove an item from an array based on the index of the item. Let's Finish writing the reducer so a new state array is returned with the item at the specific index removed.

*Code:

const immutableReducer = (state = [0,1,2,3,4,5], action) => {
  switch(action.type) {
    case 'REMOVE_ITEM':
      // Don't mutate state here or the tests will fail
      return
    default:
      return state;
  }
};

const removeItem = (index) => {
  return {
    type: 'REMOVE_ITEM',
    index
  }
}

const store = Redux.createStore(immutableReducer);
  • Answer:
const immutableReducer = (state = [0,1,2,3,4,5], action) => {
  switch(action.type) {
    case 'REMOVE_ITEM':
      let a = state.slice(0, action.index)
      let b = state.slice(action.index + 1)
      return a.concat(b)
    default:
      return state;
  }
};

const removeItem = (index) => {
  return {
    type: 'REMOVE_ITEM',
    index
  }
}

const store = Redux.createStore(immutableReducer);

Larson, Q., 2019. Frontend Development Libraries. [online] Freecodecamp.org. Available at: https://www.freecodecamp.org/learn/front-end-development-libraries/redux

28