NgRx Meta-reducers - Two Examples

Introduction

Even if you are fairly new to NgRx, you probably understand the purpose of reducers. If not, let’s look at a definition.

“Reducers in NgRx are responsible for handling transitions from one state to the next state in your application. Reducer functions handle these transitions by determining which actions to handle based on the type.” Source: https://v7.ngrx.io/guide/store/reducers

It’s important to note that reducer functions are pure functions - producing the same output for a given input. This makes them a snap to test. Basically, reducer functions handle state transitions in an immutable way, meaning the current state is not mutated to create a new state, instead copies are created and then modified. Reducers are also the consumer of actions.

Meta-reducers

So what about meta-reducers? Meta-reducers are a middleware that act as hooks into the action -> reducer pipeline. This allows for pre-processing actions before the reducers are invoked.

A meta-reducer is a function that expects a reducer and then returns a new function. Below we will see some practical examples of this.

Example: Clear State on Logout

If a user logs out of an application, it is likely we will want to clear most if not all state. Below is an example of how to do so using a meta-reducer:

import { Action, ActionReducer } from '@ngrx/store';
import { LOGOUT } from '../user/user.actions';

export function clearStateMetaReducer<State extends {}>(reducer: ActionReducer<State>): ActionReducer<State> {
    return function clearStateFn(state: State, action: Action) {
        if (action.type === LOGOUT) {
            state = {} as State; // ==> Emptying state here
        }
        return reducer(state, action);
    };
}

As you can see, when the action type is LOGOUT we set application state to an empty object ({}).

Example: LocalStorage

Another common thing we often want to do in a web application is keep the application state synced in Local Storage (Window.localStorage). To do this in NgRx, we can use a meta-reducer.

For the sake of simplicity let’s use one already available as a package:

npm install ngrx-store-localstorage --save

export function localStorageSyncReducer(reducer: ActionReducer<any>): ActionReducer<any> {
    return localStorageSync({keys: ['todos']})(reducer);
}

Adding to Application

Finally, we want to add our meta-reducers to the Store module:

const metaReducers: Array<MetaReducer<any, any>> = [localStorageSyncReducer, clearStateMetaReducer];

StoreModule.forRoot(
  { birthdays: birthdaysReducer, events: eventsReducer, user: userReducer },
  {metaReducers}
),

Conclusion

Meta-reducers provide a useful channel between actions and reducers that allow us to perform actions that we otherwise would not be able to do in the context of NgRx. As we saw common things we want to do in an application can be done by meta-reducers - clearing state on logout, keeping localStorage in sync, and others such as logging.


Erik August Johnson is a software developer working with JavaScript to build useful things. Follow them on Twitter.