Tutorial

How To Reset Redux State with a Root Reducer

Updated on August 3, 2021
    author

    Alex Jover Morales

    How To Reset Redux State with a Root Reducer

    Introduction

    Have you ever had to reset your Redux state to its initial state in your reducers? Resetting the state is something lots of apps need to do. A typical example of when the app state must be reset could be when the user logs out.

    In this article, you will learn about centralizing the resetting of the state, per-reducer reset logic, and excluding reducers from being reset.

    Prerequisites

    To follow along with this tutorial, you will need:

    • Some familiarity with React and Redux. You can refer to this post if you’re getting started with Redux.

    This tutorial was verified with redux v4.1.0.

    Understanding the Limitations of RESET_APP

    The most naive approach is to add a RESET_APP condition to all reducers.

    Consider a users reducer with the following shape:

    const usersDefaultState = [];
    
    const users = (state = usersDefaultState, { type, payload }) => {
      switch (type) {
        case 'ADD_USER':
          return [...state, payload];
        default:
          return state;
      }
    };
    

    Then you would add a RESET_APP case to the switch in order to return the usersDefaultState:

    const usersDefaultState = [];
    
    const users = (state = usersDefaultState, { type, payload }) => {
      switch (type) {
        case 'RESET_APP':
          return usersDefaultState;
        case 'ADD_USER':
          return [...state, payload];
        default:
          return state;
      }
    };
    

    This is acceptable for a reducer or two, but in any real-world app, that would lead to repeating the same code for each reducer, and you’ll most likely have many of them:

    const usersDefaultState = [];
    
    const users = (state = usersDefaultState, { type, payload }) => {
      switch (type) {
        case 'RESET_APP':
          return usersDefaultState;
        case 'ADD_USER':
          return [...state, payload];
        default:
          return state;
      }
    };
    
    const articlesDefaultState = [];
    
    const articles = (state = articlesDefaultState, { type, payload }) => {
      switch (type) {
        case 'RESET_APP':
          return articlesDefaultState;
        case 'ADD_ARTICLE':
          return [...state, payload];
        default:
          return state;
      }
    };
    

    Remember DRY (Don’t Repeat Yourself)? Well, this code doesn’t really comply with that principle and that’s something we as developers should try to avoid.

    Centralizing the Resetting of the State

    The trick to reuse the logic for resetting the app in a single place is to create a root reducer over your app root reducer. That is a reducer on top of your reducers where you check for that condition and apply it if necessary.

    Usually, you would use the combineReducers function to create a single root reducer for your redux store:

    import { combineReducers } from 'redux';
    
    const usersDefaultState = [];
    
    const users = (state = usersDefaultState, { type, payload }) => //...
    
    const articlesDefaultState = [];
    
    const articles = (state = articlesDefaultState, { type, payload }) => //...
    
    const allReducers = combineReducers({
      users,
      articles
    });
    

    In this case, allReducers is your app’s root reducer that you’ll pass to Redux’s createStore function.

    In order to create a wrapping root reducer over that one, we just need to define a function that calls that root reducer:

    const rootReducer = (state, action) => {
      return allReducers(state, action);
    }
    

    Right now, rootReducer is acting as a function that stands between the allReducers call. Right before that call is where we can add a common functionality that will apply before the reducers call. And in order to reset the app, we could just pass a state set to undefined:

    const rootReducer = (state, action) => {
      if (action.type === 'RESET_APP') {
        state = undefined;
      }
    
      return allReducers(state, action);
    }
    

    Wait, what? Why would that reset the app? Well, if we analyze the reducer definition:

    const users = (state = usersDefaultState, { type, payload }) => //...
    

    You’ll see that the default value for the state parameter is usersDefaultState in this case, and the default parameter of a function applies when and only when the received parameter value is undefined (not even null, only undefined). That’s why the state that all the reducers will receive is the default state.

    Wait, aren’t we mutating the state? Nope, we’re changing the state reference to undefined, not mutating it. Remember that mutating the state goes against Redux principles.

    Using Per-Reducer Reset Logic

    Applying the centralized reset logic using a rootReducer doesn’t prevent you from having custom functionality for the RESET_APP action in other reducers.

    For example, let’s say that the articles reducer must return a state other than the default’s one when the RESET_APP is triggered:

    const articles = (state = articlesDefaultState, { type, payload }) => {
      switch (type) {
        case "RESET_APP":
          return "App is reset";
        case "ADD_ARTICLE":
          return [...state, payload];
        default:
          return state;
      }
    };
    

    The point is, by default you’ll make all reducers return their default state, and you only will have custom reset behavior when you need it.

    Excluding Reducers From Being Reset

    It could happen that we would want to avoid having some specific reducers be reset. We could prevent that by keeping the slice of the state we want to exclude.

    For example, if you’d like to exclude the articles reducer from resetting:

    const rootReducer = (state, action) => {
      if (action.type === 'RESET_APP') {
        const { articles } = state;
        state = { articles };
      }
    
      return allReducers(state, action);
    };
    

    What’s happening here? Well, the combineReducers function combines the reducers and passes the corresponding piece of state, so that each reducer has only its part of the state in its scope.

    In this case, picking the articles piece of the state with state = { articles }, the function allReducers will pass state.articles to the articles reducer and state.users to users reducer. Since state.users is undefined and state.articles is not, only the users reducer will be reset.

    Conclusion

    In this article, you learned about centralizing the resetting of the state, per-reducer reset logic, and excluding reducers from being reset.

    There are usually multiple ways to work around code duplication, but indeed following the DRY principle makes your code more understandable and maintainable.

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

    Learn more about our products

    About the authors
    Default avatar
    Alex Jover Morales

    author

    Still looking for an answer?

    Ask a questionSearch for more help

    Was this helpful?
     
    Leave a comment
    

    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!

    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!

    Become a contributor for community

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

    DigitalOcean Documentation

    Full documentation for every DigitalOcean product.

    Resources for startups and SMBs

    The Wave has everything you need to know about building a business, from raising funding to marketing your product.

    Get our newsletter

    Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter.

    New accounts only. By submitting your email you agree to our Privacy Policy

    The developer cloud

    Scale up as you grow — whether you're running one virtual machine or ten thousand.

    Get started for free

    Sign up and get $200 in credit for your first 60 days with DigitalOcean.*

    *This promotional offer applies to new accounts only.