DEV Community

Dave Ceddia
Dave Ceddia

Posted on • Originally published at daveceddia.com on

Immutable Updates in React and Redux

How do you update state when you are not allowed to change it?

Immutability is kind of hard to wrap your head around at first. It seems like a paradox. Changing the data without changing the data? Huh?

On top of that, writing code to do immutable state updates is tricky. Below, you will find a few common patterns.

Try them out on your own, whether in the browser developer console or in a real app. Pay particular attention to the nested object updates, and practice those. I find those to be the trickiest.

All of this actually applies to React state too, so the things you learn in this guide will apply whether you use Redux or not.

Finally, I should mention that some of this code can become easier to write by using a library like Immutable.js, though it comes with its own tradeoffs. If you recoil at the syntax below, check out Immutable.js.

The Spread Operator

These examples make heavy use of the spread operator for arrays and objects. It’s represented by ... and when placed before an object or array, it unwraps the children within.

// For arrays:
let nums = [1, 2, 3];
let newNums = [...nums]; // => [1, 2, 3]
nums === newNums // => false! not the same array

// For objects:
let person = {
  name: "Liz",
  age: 32
}
let newPerson = {...person};
person === newPerson // => false! not the same object

// Internals are left alone:
let company = {
  name: "Foo Corp",
  people: [
    {name: "Joe"},
    {name: "Alice"}
  ]
}
let newCompany = {...company};
newCompany === company // => false! not the same object
newCompany.people === company.people // => true!
Enter fullscreen mode Exit fullscreen mode

When used as shown above, the spread operator makes it easy to create a new object or array that contains the exact same contents as another one. This is useful for creating a copy of an object/array, and then overwriting specific properties that you need to change:

let liz = {
  name: "Liz",
  age: 32,
  location: {
    city: "Portland",
    state: "Oregon"
  },
  pets: [
    {type: "cat", name: "Redux"}
  ]
}

// Make Liz one year older, while leaving everything
// else the same:
let olderLiz = {
  ...liz,
  age: 33
}
Enter fullscreen mode Exit fullscreen mode

The spread operator for objects is a stage 3 draft, which means it’s not officially part of JS yet. You’ll need to use a transpiler like Babel to use it in your code. If you use Create React App, you can already use it.

Recipes for Updating State

These examples are written in the context of returning state from a Redux reducer: pretend that the state = {whatever} at the top is the state that was passed in to the reducer, and then the updated version is returned underneath.

Applying to React and setState

To apply these examples to plain React state, you just need to tweak a couple things:

return {
  ...state,
  updates here
}

// becomes:
this.setState({
  ...this.state,
  updates here
})
Enter fullscreen mode Exit fullscreen mode

Here are some common immutable update operations:

Updating an Object

const state = {
  clicks: 0,
  count: 0
}

return {
  ...state,
  clicks: state.clicks + 1,
  count: state.count - 1
}
Enter fullscreen mode Exit fullscreen mode

Updating a Nested Object

const state = {
  house: {
    name: "Ravenclaw",
    points: 17
  }
}

// Two points for Ravenclaw
return {
  ...state,
  house: {
    ...state.house,
    points: state.house.points + 2
  }
}
Enter fullscreen mode Exit fullscreen mode

Updating an Object by Key

const state = {
  houses: {
    gryffindor: {
      points: 15
    },
    ravenclaw: {
      points: 18
    },
    hufflepuff: {
      points: 7
    },
    slytherin: {
      points: 5
    }
  }
}

// Add 3 points to Ravenclaw,
// when the name is stored in a variable
const key = "ravenclaw";
return {
  ...state,
  houses: {
    ...state.houses,
    [key]: {
      ...state.houses[key],
      points: state.houses[key].points + 3
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Add an element to the beginning of an array

const array = [1, 2, 3];
const newItem = 0;
return [
  newItem,
  ...array
];
Enter fullscreen mode Exit fullscreen mode

Add an element to the end of an array

const array = [1, 2, 3];
const newItem = 4;
return [
  ...array,
  newItem
];
Enter fullscreen mode Exit fullscreen mode

Add an element in the middle of an array

Pro tip: Write unit tests for these things. It’s easy to make off-by-one errors.

const array = [1, 2, 3, 5, 6];
const newItem = 4;
return [ // array is new
  ...array.slice(0, 3), // first X items unchanged
  newItem,
  ...array.slice(3) // last Y items unchanged
];
Enter fullscreen mode Exit fullscreen mode

Change an element in the middle of an array

This is the same pattern as adding an item, except that the indexes are different.

Pro tip: Write unit tests for these things. It’s easy to make off-by-one errors.

const array = [1, 2, "X", 4];
const newItem = 3;
return [ // array is new
  ...array.slice(0, 2), // first X items unchanged
  newItem,
  ...array.slice(3) // last Y items unchanged
];
Enter fullscreen mode Exit fullscreen mode

All Done

Was this helpful? Did I miss a pattern you wanted to see? Leave a comment below.

Immutable Updates in React and Redux was originally published by Dave Ceddia at Dave Ceddia on November 29, 2017.

CodeProject

Top comments (0)