Author avatar

Adam Garrett-Harris

How to Write a Redux Reducer

Adam Garrett-Harris

  • Oct 15, 2019
  • 5 Min read
  • 19 Views
  • Oct 15, 2019
  • 5 Min read
  • 19 Views
Web Development
React

Introduction

In Redux, a reducer is a pure function that takes an action and the previous state of the application and returns the new state. The action describes what happened and it is the reducer's job to return the new state based on that action.

1
(previousState, action) => newState
js

It may seem simple, but it does have to be a pure function with no side effects.

Pure Function

Pure functions can't do any of the following:

  1. Access global variables
  2. Change global variables
  3. Mutate its arguments
  4. Perform side effects (like make API calls)
  5. Call any other impure function (like date and random functions)

Actions

Actions are objects that have a type property and any other data that it needs to describe the action. For example, a chat application might have this action:

1
2
3
4
5
{
  type: 'ADD_MESSAGE',
  channel_id: 'channel-1',
  message: 'Hello there!'
}
js
1
2
3
4
{
  type: 'ADD_MESSAGE',
  message: 'Hello there!'
}
js

State

The state is an object that describes aspects of your application that can change over time. You should make your state the minimal amount of data that's required to describe the state of the application. For example, our chat application might have this state:

1
2
3
4
5
6
7
8
9
10
11
12
{
  userId: 'user-1234',
  username: 'Adam',
  messages: [{
    user: 'Adam',
    message: 'Hi',
  },
  {
    user: 'Sarah',
    message: 'Hello there!',
  }],
}
js

Action Types

It may seem verbose, but it's useful to keep a file called actions.js to export all of the action types as strings.

1
export const CHANGE_USERNAME = 'CHANGE_USERNAME'
js

Then you can import them in the reducer.js so that you don't accidentally misspell the action name.

1
import { CHANGE_USERNAME } from './actions'
js

The Reducer

The reducer takes two parameters: state and action. You need to have an initial value so that when Redux calls the reducer for the first time with undefined, it will return the initialState. Then the function uses a switch statement to determine which type of action it's dealing with. If there is an unknown action, then it should return the state, so that the application doesn't lose its current state.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const initialState = {
  username: '',
  messages: [],
}

function reducer(state = initialState, action) {
  switch (action.type) {
    case CHANGE_USERNAME:
      return {
        ...state,
        username: action.username,
      }
    default:
      return state
  }
}
js

Let's look closer at the return statement in that reducer:

1
2
3
4
return {
  ...state,
  username: action.username,
}
js

We're returning a brand new object rather than trying to change state. We then use the spread operator to create a copy of the state. Then we override the username property with the new value from action.username.

Adding to an Array

You can add as many case statements as you need. Let's write another one for adding a new message.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function reducer(state = initialState, action) {
  switch (action.type) {
    case CHANGE_USERNAME:
      return {
        ...state,
        username: action.username,
      }
    case ADD_MESSAGE:
      return {
        ...state,
        messages: [
          ...state.messages,
          {
            user: state.userId,
            message: action.message
          },
        ],
      }
    default:
      return state
  }
}
js

Here we do something similar to the first case. We return a new object and spread state inside of it. Then we override messages and give it the value of a brand new array, but we want to keep all of the existing messages, so we spread state.messages inside it. Then we add a new object to the end of the array with the user from the state and the message from the action.

Conclusion

I gave examples of changing the state by changing a string and adding to an array, but there are a lot more things you can do in a reducer that can get very complex. You can remove an item from an array (with Array.slice and the spread operator), change multiple items in an array (with Array.map), remove multiple items from an array (with Array.filter), or remove multiple items from an object (with Object.keys, .filter(), and .reduce()).

Just remember to keep it pure and don't mutate the state. And since it's a pure function you can easily unit test your reducer.

About the Author

I’m a full-stack software engineer and host of the BookBytes podcast. You can follow me on Twitter.

1