Simplify Your Redux Reducers with Immer
Tags: redux, react, immer, javascript
Published: 2019-09-12 13:30:54

---
title: Simplify Your Redux Reducers with Immer
published: true
description: Learn how Immer makes your Redux reducers easier to write and maintain.
tags: redux, react, immer, javascript
canonical_url: https://stevenmercatante.com/simpify-redux-reducers-with-immer/
---

___
*This post was originally published at [https://stevenmercatante.com](https://stevenmercatante.com/simpify-redux-reducers-with-immer/)*
___


## The Problem

I was using Redux for a project and saw that my reducers were getting kind of gnarly. The code seemed to grow exponentially whenever I needed to work with nested data. This was due to Redux's insistence on using immutable data. I'm a big fan of using immutable data, but it's definitely more... awkward to work with compared to mutable data. Let's look at some examples:

```javascript
case ADD_TIMER: {
  return { [action.payload.id]: action.payload, ...state };
}
```

I know what you're thinking... "that code isn't awkward - it's just using the spread operator to add an item to an existing object. Easy!" Fine, let's keep going...

```javascript
case REMOVE_TIMER: {
  const newState = { ...state };
  delete newState[action.payload.id];
  return newState;
}
```

OK, that's still not too bad, but all I want to do is delete an item from an object. I shouldn't need to create a copy of the existing state, delete the item from the copy, and then return the copy.

```javascript
case INCREMEMT_RUNNING_TIMERS: {
  const updatedTimers = Object.values(state)
    .filter(timer => timer.running)
    .reduce((acc, timer) => {
      timer.totalTime = getTotalTime(true, timer.starts, timer.stops);
      acc[timer.id] = timer;
      return acc;
    }, {});
  return { ...state, ...updatedTimers };
}
```

Good luck convincing me this one can't be improved. In case you're wondering, I'm iterating over the object, filtering on only the ones I want, reducing them into a new object with some updated properties, and finally merging that into the returned state. Yikes.

## The Solution

[Immer](https://github.com/immerjs/immer) to the rescue! Immer lets you "Create the next immutable state tree by simply modifying the current tree." What's that mean? Let's convert the above code examples to see.

```javascript
case ADD_TIMER: {
  draft[action.payload.id] = action.payload;
  break;
}
```

```javascript
case REMOVE_TIMER: {
  delete draft[action.payload.id];
  break;
}
```

```javascript
case INCREMEMT_RUNNING_TIMERS: {
  Object.values(draft).forEach(timer => {
    if (timer.running) {
      timer.totalTime = getTotalTime(true, timer.starts, timer.stops);
    }
  });
  break;
}
```

_(Don't worry about that `draft` variable - we'll talk about that in just a bit...)_

Look at that! The code is shorter, easier to read, and easier to understand. But, doesn't this break Redux's need for immutable operations? Nope. Immer is performing immutable operations behind the scenes, but it lets us write mutable operations, which 9 times out of 10, are easier to reason about (not to mention quicker to write). The secret is Immer's concept of a `draftState`.

## The Draft State

Rather than explain it myself, here's how Immer defines the `draftState`:

> "The basic idea is that you will apply all your changes to a temporary draftState, which is a proxy of the currentState. Once all your mutations are completed, Immer will produce the nextState based on the mutations to the draft state. This means that you can interact with your data by simply modifying it while keeping all the benefits of immutable data."

You need to add a little bit of code to your reducer. Here's a complete example:

```javascript
import produce from 'immer'

export default (state = {}, action) =>
  produce(state, draft => {
    switch (action.type) {
      case ADD_TIMER: {
        draft[action.payload.id] = action.payload
        break
      }

      case REMOVE_TIMER: {
        delete draft[action.payload.id]
        break
      }

      case INCREMEMT_RUNNING_TIMERS: {
        Object.values(draft).forEach(timer => {
          if (timer.running) {
            timer.totalTime = getTotalTime(true, timer.starts, timer.stops)
          }
        })
        break
      }

      default:
        return draft
    }
  })
```

Make sure you don't forget that call to `produce` - your reducer won't work without it!

## Wrapping Things Up

Immer has become one of my go-to tools whenever I work on a Redux project. It takes minimal time to explain to colleagues & contributors, and provides a bunch of benefits, including:

- less code to write
- less code to maintain
- code that's easier to understand

And in case you need further convincing, check out what one of React's core maintainers has to say about it:

<blockquote className="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">If you like MobX, I highly recommend following along <a href="https://twitter.com/mweststrate?ref_src=twsrc%5Etfw">@mweststrate</a>’s work on Immer. While MobX is pretty far removed from the vision of where we’re going with React. Immer is dead on.</p>&mdash; Sebastian Markbåge (@sebmarkbage) <a href="https://twitter.com/sebmarkbage/status/1032684851063705600?ref_src=twsrc%5Etfw">August 23, 2018</a></blockquote>

👋 **Enjoyed this post?**

Join my [newsletter](https://stevemerc.com/newsletter/?r=dev) and follow me on Twitter [@mercatante](https://twitter.com/mercatante) for more content like this.