Tutorial

Higher-Order Reducers in Redux

Published on June 11, 2018
    Default avatar

    By Alex Jover Morales

    Higher-Order Reducers in Redux

    While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the ā€œreport an issueā€œ button at the bottom of the tutorial.

    In Resetting Redux State weā€™ve seen a way to reuse reducer logic to reset the state by creating a root reducer. But thatā€™s not the only way to reuse reducer logic, and higher-order reducers come into the scene to help us with reusability and reducing code duplication.

    Higher-order reducers, unlike the root reducer approach, allow us to apply a certain functionality to the reducers that need it. They do so using functional composition, so letā€™s first learn that concept.

    Higher-Order Function

    A higher order function is a function that returns another function as its result. For example, if we have a plain function with the following two arguments:

    const greet = (first, second) => console.log(first + second)
    
    greet('Hi ', 'little kitty'); // Hi little kitty
    

    We could re-write it in a higher-order function by separating their arguments in two nested functions:

    const greet = first => second => console.log(first + second)
    
    greet('Hi ')('little kitty'); // Hi little kitty
    

    Notice the call to greet('Hi ') function, which returns a function that we are calling as ('little kitty'). The point is, this approach allow functional composition for code reuse.

    Why doing that, you say? Imagine you want to use the greet function to say Hi ${someone} several times in your code. If you do it with a simple plain function as the first example, youā€™ll need to repeat Hi several times:

    greet('Hi ', 'dolphin');
    greet('Hi ', 'camel');
    greet('Hi ', 'snake');
    

    While using a higher order function, we have the ability to easily create a sayHi function:

    const sayHi = greet('Hi ');
    
    sayHi('dolphin 🐬');
    sayHi('camel 🐫');
    sayHi('snake 🐍');
    

    You can already see two benefits:

    • Weā€™re creating a more semantic sayHi function based on a more generic one
    • Weā€™re not repeating 'Hi '

    Of course, this is a simplistic example thatā€™s merely for education purposes, but that greet function could be something which performs heavier logic, where the mentioned benefits will be much more obvious.

    Higher-Order Reducers

    Now you understand the mechanics of a higher-order function, you might be wondering: howā€™s that related to reducers? Well, if you think about it, a reducer is simply a function, so when we apply this pattern to reducers we call them higher-order reducers.

    Letā€™s go over an example. Imagine that you have a users and articles reducers, which have a data property that comes from an API. Something like:

    const defaultState = {
      data: []
    };
    
    const userReducer = (state = defaultState, action) => {
      //...
    }
    

    Say that both of the users and articles need to have pagination. Then weā€™ll need to have some actions like GO_NEXT_PAGE, GO_PREV_PAGE, UPDATE_TOTAL_PAGES, etc. It becomes really cumbersome when you need to duplicate all that logic in each of your reducers that need pagination.

    Hereā€™s where we can create a higher-order reducer that, given a reducer, returns a decorated reducer with the additional functionality. Letā€™s call it withPagination:

    const withPagination = reducer => (state, action) => {
      switch(action.type) {
        case 'GO_NEXT_PAGE':
          return { ...state, page: state.page + 1 }
        // ...
        default:
          return reducer(state, action);
      }
    };
    

    Isnā€™t this familiar? Yes, the nested function is a reducer. Remember that a reducer has the (state, action) signature.

    Whatā€™s happening here is that, based on a reducer given as an argument, weā€™re returning a new reducer function that adds the GO_NEXT_PAGE logic, and on the default statement of the switch, we simply are proxying the call to the original reducer. So with this, weā€™re adding some functionality only when needed.

    Using a Higher-Order Reducer

    We can apply the already created withPagination higher-order reducer to the reducers weā€™d like, for example when calling the combineReducers function to create the appā€™s root reducer:

    import { combineReducers } from 'redux';
    
    import withPagination from './higher-order-reducers/withPagination'
    
    import users from './reducers/users';
    import articles from './reducers/articles';
    import login from './reducers/login';
    
    
    const rootReducer = combineReducers({
      users: withPagination(users),
      articles: withPagination(articles),
      login, // we don't need pagination for the login reducer
      // ...
    });
    

    Notice that weā€™re applying the pagination logic only to the users and articles reducers. Thatā€™s the power of higher order reducers: we can apply them as needed.

    Parametrizing Higher-Order Reducers

    We still have a problem with the previous example: when we trigger the GO_TO_NEXT_PAGE action, both the users and articles will handle that actions since they both are checking the same action name.

    As you can guess, that can be solved by specifying different action names for different reducers, so weā€™ll have the USERS_GO_TO_NEXT_PAGE and ARTICLES_GO_TO_NEXT_PAGE actions.

    We can make that happen by passing a section parameter to the withPagination higher order reducer:

    const withPagination = (section, reducer) => (state, action) => {
      switch(action.type) {
        case `${section}_GO_NEXT_PAGE`:
          return { ...state, page: state.page + 1 }
        // ..
      }
    };
    
    // ...
    
    
    const rootReducer = combineReducers({
      users: withPagination('USERS', users),
      articles: withPagination('ARTICLES', articles),
      login
    });
    

    Wrapping Up

    Weā€™ve seen the higher-order reducer pattern and how it solves code duplication and improves reusability by using function composition.

    I hope youā€™ve learned something new here, and that you can see how there are multiple patterns we can apply to Redux or our code in general given Reduxā€™s functional nature.

    Stay cool šŸ¦„

    Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

    Learn more about us


    About the authors
    Default avatar
    Alex Jover Morales

    author

    Still looking for an answer?

    Ask a questionSearch for more help

    Was this helpful?
    Ā 
    2 Comments
    ļ»æ

    This textbox defaults to using Markdown to format your answer.

    You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

    A-MA-ZING! bring more of those please! Iā€™d say this is part of advance react topics

    Great article, thanks!

    Try DigitalOcean for free

    Click below to sign up and get $200 of credit to try our products over 60 days!

    Sign up

    Join the Tech Talk
    Success! Thank you! Please check your email for further details.

    Please complete your information!

    Get our biweekly newsletter

    Sign up for Infrastructure as a Newsletter.

    Hollie's Hub for Good

    Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

    Become a contributor

    Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

    Welcome to the developer cloud

    DigitalOcean makes it simple to launch in the cloud and scale up as you grow ā€” whether you're running one virtual machine or ten thousand.

    Learn more
    DigitalOcean Cloud Control Panel