Understanding Reducers from scratch

Arseniy Tomkevich
5 min readAug 29, 2018

--

One of the core concepts that have recently changed how we structure our UI applications is the reducer.

But what is a reducer? And why should I care about it as a concept?

Reducer is a simple function that takes two values and reduces them down to one value. A reducer function must be pure and predictable.

function (a, b) { return a + b; }
function (a, b) { return a / b; }
function (a, b) { return (a + b)/2; }

Simple eh?

Now that we understand what is a basic reducer, lets expand on that idea.

For example, the following reducer (addReducer) takes a state value and adds a change value to it, effectively reducing both values into one, and returns the result as new state object.

The examples I am using here are simple mathematical functions, but these could be functions that manipulate a nested object. As long as the function result is predictable and would be easy to unit test.

In the example bellow, in one line we execute the reducer 3 times, passing in the result of each reducer as state and adding to the state value with every execution.

After 3 reductions, the state value grows from 5 to 15.

console output:
sum reducer result : 15

In this context, a reducer is a function that takes a state object and somehow applies some change to it, creating a new state.

Now, lets introduce another reducer that applies change in a different way. The removeReducer, which will subtract a value from state, creating a new state.

console output:
state after 3 addReducer’s applied : 15
state after 3 removeReducer’s applied : 6

I am hoping you are getting the concepts down. So far we have a couple of reducers, each applies change to a state. The addReducer adds to the state. The removeReducer subtracts from the state.

Whats good about using reducers is we know exactly how they will affect the state.

But things get more interesting when instead of manually executing reducers, we start applying arrays of value changes. Luckily, the native Array reduce() method can help us with that. Lets re-write our prior example using it.

console output:
state after 3 addReducer’s applied : 15
state after 3 removeReducer’s applied : 6

So the result is the same, which is exactly what we wanted. Except now reducers apply the changes through Array.reduce() method using a sequence of changes as input.

At this point the mechanism is somewhat primitive. We mutate a state through reducer but we do it in a sequence of consecutive changes.

When in a real world application events are asynchronous, like user input, database updates or image loading. We never know which will happen next. Some application events will need to perform more than one change, and execute more than one reducer.

In order to support asynchronous behavior with our reducer mechanism, we’ll need to improve the design further by:

  • turning change values into objects that instead describe change, ie actions
  • handling the reducer logic in one central location

console output:
state after all actions applied : 6

Now, the changes array, instead of values, holds a series of objects, each describes the action to perform within the reducer. This is a very important concept, let that sink in.

Internally the reducer deconstructs an action object into type & change variables. It then uses type to figure out which reduction should be applied on to a state object.

This is great, because action objects decouple application events from executing reducers directly.

The design could be further improved if

  • We got away from using object notation for actions by adding an easier way to generate actions with Action class
  • Introduce action creator functions that generate an Action from their function parameters
  • Implement addChange to add Action to changes array immediately

console output:
state after all actions applied : 6

New design will be able to support custom asynchronous events and is much closer in design to Redux, a popular Javascript library based.

The premise is straight forward. For every asynchronous event in the application, we will generate an action object via action creators. The action object will be pushed into a queue of changes, which then gets processed by the reducer.

--

--

Arseniy Tomkevich

Tech Aficionado, Leader, Polyglot Programmer with a passion for state-of-the-art technology, cybernetics, and fatherhood