Comments

Understand The Redux Reducer

Redux has been one of the most popular choice for state management in frontend applications. But there has always been one recurring complaint about Redux. “Redux is hard to understand!” Many developers find redux confusing. I think some of this is because, Redux follows functional programming concepts that many developers are not used to. This could be the challenge to learn Redux.

In this post, I am going to try to break things up a bit, and get to the basics. At a conference talk in All Things Open last year, I spoke about Redux and integrating Redux to your application. I gave an analogy about the Redux reducer, which many of the attendees really appreciated. In this blog post I am going to use that analogy and try to explain Reducers in Redux.

What is a Reducer?

What is a Reducer ? And how do I write a reducer without messing up?

I like to think of a Reducer in Redux as a “Coffee Maker”. It takes in an old state and action and brews a new state (Fresh coffee).

I like to think of a reducer like a “coffee maker”. The coffee maker takes in coffee powder and water. It then returns a freshly brewed cup of coffee that we can enjoy. Based on this analogy reducers are functions that take in the current state (coffee powder) and actions (water) and brew a new state (fresh coffee).

Reducers are pure functions that take in a state and action and return a new state.

A reducer should always follow the following rules:

Given a set of inputs, it should always return the same output. No surprises, side effects, API calls, mutations.

What are Pure Functions?

Redux reducers are pure functions. Now what are pure functions??

A function is considered pure, if it adheres to the following rules:

  1. The function always returns the same output if the same arguments are passed in.
  2. The function does not produce any side-effects.

We have all written pure functions in the past knowingly or unknowingly.
Take a look at a pure function below, that returns a sum of two numbers given two input arguments.

function sumOfNumbers(a, b) {
 return a + b;
}

The output of this function will always remain the same, if the same input arguments are passed in. Simple enough. Pure functions are simple building blocks that have a predictable behavior always. Hence they always return the same predictable output, without any external influence.

Here is a simple example, on what an impure function looks like.

var value = 5;
function impure(arg) {
    return value + 2 + 3;
}

Here the function is considered impure, because it is not using the argument that was passed to it. Instead, it uses an external value which is subject to change. Therefore, making the function impure due to external interference/side-effects.

What are Side-Effects?

Side effects occur anytime your function interacts with the outside world.

Some examples of common side-effects include:

  • Making an API call
  • Mutating data
  • console logs to the screen
  • Manipulating the DOM
  • Date.now() to get current date/time
  • async await calls/waiting for promises to resolve
  • math.random() to generate random numbers

Well, you may wonder how we can write functions without side-effects all the time. It is quite challenging to avoid side-effects altogether. All I am saying is, for a function to qualify as pure, as the name suggests, it should have no side-effects.

Never Mutate State Inside The Reducer

We have to remember that we should never mutate state inside the reducer and this can be achieved with either the Object.assign() or the latest Object Spread (…) operator.

Never mutate state, always return a new copy of state.

Since the spread operator is easier to understand and is supported in the latest javascript versions, I am going to use it in my example.

The object spread operator is conceptually similar to the ES6 array spread operator.

Let’s walkthrough an example where you are going to rate this blog post. Our reducer will take the old state and the action. The action here is RATE_BLOG_POST. The reducer method blogDetails() will take the current state and action as the parameters and return a new state. Remember the coffee maker analogy again!

// Reducer
function blogDetails(state = initialState, action) {
  switch (action.type) {
     case RATE_BLOG_POST:
       return { 
         ...state, rating: action.rate 
       }
     default:
       return state
    }
 }

With the example above we are always going to return a new copy of the state. The spread operator (…) will ensure that there are no mutations of state within the reducer function.

Multiple Reducers For Larger Apps

Redux follows the single store principle to maintain one immutable store. But it allows you to have multiple reducers. As your app starts growing you can have multiple reducers separated by functionalities. This makes it easier to separate concerns across multiple reducers. In the example below we have two reducers A and B. And the combineReducer function from redux can be used to combine the reducers and pass them down to the single store.

import { createStore, combineReducers } from 'redux';  
// The A Reducer 
const AReducer = function(state = {}, action) {   
   return state 
}  
// The B Reducer 
const BReducer = function(state = {}, action) {   
   return state 
}  
// Combine Reducers 
const reducers = combineReducers({   
   AState: AReducer,   
   BState: BReducer 
})  
const store = createStore(reducers)

Benefits of Reducer

Reducers are the major portion of the redux integration and hold a lot of the business logic. Since reducers are designed to be pure functions, they carry a lot of benefits along.

  • Business logic is maintained in clean, simple and elegant functions because they are pure functions.
  • Reducers are pure functions, and therefore easier to maintain, test and debug.
  • It is also easy to extend and add additional functionality to the reducers.

Of course, writing clean pure functions is possible even if you do not use redux. But the fact that when we use redux in complex apps for state management, adhering to these principles is awesome.

Conclusion

Ultimately, Redux is just a standalone state management library and you don’t have to use it for every single application. This blog post by Dan Abramov (creator of Redux) on Why You May Not Need Redux is a great read. This will help you decide if you really need Redux at all.

There are also other state management libraries like MobX that are popular which could be a good option as well. I had written a post earlier with differences between Redux vs. MobX that could help you choose between the two.

Moreover with the addition of hooks and context you could possibly do away with state management libraries like Redux in React. Nevertheless, I like Redux and have used it in several projects successfully.

We have reached the end of this post. If you enjoyed this post, please share it and leave a comment on whether you use Redux for your app. You can follow me on twitter @AdhithiRavi

Adhithi Ravichandran is a Software Consultant based in Kansas City. She is currently working on building apps with React, React Native and GraphQL. She is a Conference Speaker, Pluralsight Author, Blogger and Software Consultant. She is passionate about teaching and thrives to contribute to the tech community with her courses, blog posts and speaking engagements.
Tags: , ,

Leave a Reply

Connect with Me
  • Categories
  • Popular Posts

    %d bloggers like this: