What is a Redux reducer?

By Dave Ceddia

reducer, n. – A word Redux made up to confuse you.

In order to work with Redux, you need to know a few things. One of those things is what a reducer is and what it does. It might seem a bit scary and foreign, but after this short article I think you’ll come to agree that it is, as the saying goes, “just a function.”

First off, where does the name “reducer” come from? Redux didn’t actually make it up (I was kidding about that). It might not seem too foreign if you’re familiar with functional programming and JavaScript’s Array.reduce function. And if you know Array.reduce, you know that it takes a function (one might call it a “reducer” function) that has the signature (accumulatedValue, nextItem) => nextAccumulatedValue.

Array.reduce is like a sister to Redux

If you aren’t yet familar with Array.reduce, here’s what’s up:

JavaScript’s Array has a built-in function called reduce.

(Technically I should be writing it as Array.prototype.reduce, because it is a function on array instances, not on the capital-A Array constructor.)

It takes a function as an argument, and it calls your provided function once for each element of the array, similar to how Array.map works (or a for loop, for that matter). Your function gets called with 2 arguments: the last iteration’s result, and the current array element. This will make more sense with an example:

var letters = ['r', 'e', 'd', 'u', 'x'];

// `reduce` takes 2 arguments:
//   - a function to do the reducing (you might say, a "reducer")
//   - an initial value for accumulatedResult
var word = letters.reduce(
  function(accumulatedResult, arrayItem) {
    return accumulatedResult + arrayItem;
  },
''); // <-- notice this empty string argument: it's the initial value

console.log(word) // => "redux"

In this example, the reducer will get called 5 times (because there are 5 elements in the array). The calls go like this:

  • first called with ('', 'r') => returns 'r'
    • the empty string '' comes from the 2nd argument to reduce, and the 'r' is the first element of the array
  • then ('r', 'e') => returns 're'
    • the ‘r’ comes from the previous return value, and ‘e’ is the next element of the array
  • then ('re', 'd') => returns 'red'
    • the ‘re’ is the previous return value, and ‘d’ is the third array element
  • then ('red', 'u') => returns 'redu'
    • by now you are sensing a pattern
  • then ('redu', 'x') => returns 'redux'
    • the pattern is all too clear now

The last return value, 'redux', is returned as the final result and stored in the word variable.

Redux Reducers

Now that you know how Array.reduce works, I can tell you that Redux is basically a fancy Array.reduce function (ok ok that’s a huge oversimplification, but bear with me).

A Redux reducer function has this signature:

(state, action) => newState

As in: it takes the current state, and an action, and returns the newState. Looks a lot like the signature of an Array.reduce reducer, eh? Remember:

(accumulatedValue, nextItem) => nextAccumulatedValue

Plainly speaking, a Redux reducer gets to decide how each action affects the state. Let’s look at an example one:

function wordReducer(state = '', action) {
  switch(action.type) {
    case 'ADD_LETTER':
      return state + action.letter;
    case 'RESET':
      return '';
    default:
      return state;
  }
}

Quick quiz: is there any Redux-specific code in here? Anything that depends on the Redux library to work? Go ahead, think it over, I’ll wait.

Answer: Nope! This is a plain old function. Sure, it takes (state, action) arguments and returns a new state. And it expects action to look something like {type: 'ADD_LETTER', letter: 'r'}. But none of that is particularly bound to Redux.

How it Works

But anyway, what does it actually do? Let’s try calling it with a few things and see what it returns.

let state = '';
console.log(wordReducer(state, {type: 'ADD_LETTER', letter: 'y'}));
  // => y
console.log(wordReducer(state, {type: 'ADD_LETTER', letter: 'y'}));
  // => y
console.log(wordReducer(state, {type: 'ADD_LETTER', letter: 'y'}));
  // => y

First: notice that wordReducer does not remember anything. It holds no state within.

let state = '';
console.log(wordReducer(state, {type: 'ADD_LETTER', letter: 'a'}));
  // => a
console.log(wordReducer(state, {type: 'ADD_LETTER', letter: 'b'}));
  // => b
console.log(state)
  // => ''

Next: notice that wordReducer does not change the state. It merely returns a new one. It treats state as immutable. This is important because, by updating state in an immutable way, Redux is able to tell which pieces of state changed, and optimize how your app is re-rendered.

One more thing:

console.log(wordReducer(undefined, {type: 'UNHANDLED'}));
  // => ''
console.log(wordReducer('existing state', {type: 'UNHANDLED'}));
  // => 'existing state'

Notice that the reducer has an initial state (when given undefined, it returns an empty string anyway), and that it has a default case that handles any actions it doesn’t understand (it returns the existing state, unchanged, when it sees such an action).

Pilot Needed

I can tell you don’t think this is very useful. What good is a function that doesn’t remember anything, and doesn’t change anything?

I’ll tell you: This function is nice because it is predictable. If you call it with the same arguments, you get the same outputs, every single time. It doesn’t matter what else has changed in your app – this function will always act the same way.

It is easy to figure out what it does by reading its code (and easy to debug!) because it’s all self-contained.

Now, the downside with a function like this is that it needs a driver of sorts. Something needs to hold on to the intermediate state, otherwise the app won’t really do much of anything.

The driver, in this case, is Redux. Specifically, the Redux store. It does something like this:

let state = '';
state = wordReducer(state, {type: 'ADD_LETTER', letter: 'a'}));
state = wordReducer(state, {type: 'ADD_LETTER', letter: 'b'}));
state = wordReducer(state, {type: 'ADD_LETTER', letter: 'c'}));

The store maintains an internal state variable. When an action is dispatched, the store calls the reducer, and replaces its internal state with whatever the reducer returned. Every time the store calls the reducer, it passes in the last-known state.

Around and around it goes: Redux sitting there waiting for an action, handling that action, updating the state, re-rendering your app, on and on forever.

So that’s it! That’s how Redux reducers work, in a nutshell. Not too bad?

Further Reading

Working through learning Redux right now? Here are a few more articles to get you there: