The Definitive Guide to Redux Persist

Persist your Redux state in between app launches with Redux Persist

Mark Newton
React Native Coach
Published in
7 min readDec 28, 2017

--

Redux Persist takes your Redux state object and saves it to persisted storage. Then on app launch it retrieves this persisted state and saves it back to redux.

Notes:

  • This guide is for v5 of redux-persist, which was released in October 2017.
  • Parts of this guide were merged into the official docs via this pull request, which I submitted. However, this guide is still your best source for gaining an understanding of the library.

Quickstart

Dependencies

npm install --save redux-persist - OR - yarn add redux-persist

Implementation

When creating your redux store, pass your createStore function a persistReducer that wraps your app’s root reducer. Once your store is created, pass it to the persistStore function, which ensures your redux state is saved to persisted storage whenever it changes.

If you are using React, wrap your root component with PersistGate. This delays the rendering of your app’s UI until your persisted state has been retrieved and saved to redux.

Customizing what’s Persisted

If you don’t want to persist a part of your state you could put it in the blacklist. The blacklist is added into the config object that we used when setting up our PersistReducer.

The blacklist takes an array of strings. Each string must match a part of state that is managed by the reducer you passed to persistReducer. For the example above, if rootReducer was created via the combineReducers function we would expect ‘navigation’ to appear there, like so:

The whitelist is set up in the same way as the blacklist except that it defines the parts of state that you do want to persist.

What if you wanted to blacklist a nested property though? For example, let’s say your state object has an auth key and that you want to persist auth.currentUser but NOT auth.isLoggingIn.

To do this, wrap your AuthReducer with a PersistReducer, and then blacklist the isLoggingIn key. This allows you to co-locate your persistence rules with the reducer it pertains to.

If you prefer to have all your persistence rules in one place, instead of co-located with their associated reducer, consider putting it all with your combineReducers function:

The Merge Process

When your app launches, redux sets an initial state. Shortly after this, Redux Persist retrieves your persisted state from storage. Your persisted state then overrides any initial state.

The merge process is meant to “just work” automatically for you. However, you can also take manual control of the process. For example, in older versions of Redux Persist it was common to manage the rehydration process by catching the REHYDRATE action in your reducers and then saving the action’s payload to your redux state.

The REHYDRATE action is dispatched by Redux Persist immediately after your persisted state is obtained from storage. If you return a new state object from the REHYDRATE action, this will be your finalized state. As mentioned though, you don’t need to do this anymore unless you need to customize the way your state is rehydrated.

Watch out for the huge gotcha…

There is a gotcha when it comes to the merge process, and it has to do with how deeply the merge process looks inside your state for changes. We mentioned that the merge process overrides your initial state with whatever was persisted. Here is how that works by default.

Let’s say our initial state looks like this, and that we are persisting the entire thing.

Our app launches, and here is our persisted state.

By default, the merge process simply replaces each top-level piece of state. In code, this looks similar to the following:

This usually works fine, but what if you released a new version of your app that sets your initial auth state like this.

You’d obviously want your final state object to include this new error key. But your persisted state object doesn’t yet have this error key, and its going to completely replace your initial state during the rehydration process. Bye bye error key.

The fix for this is to tell your PersistReducer to merge two-levels deep. In the Quickstart section, you may have noticed a mysterious stateReconciler setting for our root PersistReducer.

autoMergeLevel2 is how you merge two-levels deep. For the auth state, this means that the merge process will first make a copy of the initial auth state, and then only override the keys within this auth object that were persisted. Since ‘error’ wouldn’t have been persisted yet, it would be left alone.

In summary, it’s important to know that PersistReducers default to autoMergeLevel1, which means they replace top-level state with whatever was persisted. If you don’t have a separate PersistReducer managing the persisted state for these top-level keys, you’ll probably want to use autoMergeLevel2.

Interesting tidbit: the author of Redux Persist realized that choosing between autoMergeLevel1 and autoMergeLevel2 can be confusing. So he created a function called persistCombineReducers in an attempt to simplify things. This function’s implementation is two lines of code, and simply returns a PersistReducer defaulted to autoMergeLevel2. My personal preference is to set the merge level myself, and not use this function. But it’s up to you.

Advanced Customization

Transforms

Transforms allow you to customize the state object that gets persisted and rehydrated.

​When the state object gets persisted, it first gets serialized with JSON.stringify(). If parts of your state object are not mappable to JSON objects, the serialization process may transform these parts of your state in unexpected ways. For example, the javascript Set type does not exist in JSON. When you try to serialize a Set via JSON.stringify()​, it gets converted to an empty object. Probably not what you want.

Below is a Transform that successfully persists a Set​ property, which simply converts it to an array and back. In this way, the Set gets converted to an Array, which is a recognized data structure in JSON. When pulled out of the persisted store, the array gets converted back to a Set before being saved to the redux store.

The createTransform function takes three parameters.

  • A function that gets called right before state is persisted.
  • A function that gets called right before state is rehydrated.
  • A config object.

Lastly, transforms need to be added to a PersistReducer’s config object.

That’s all for now…

Have questions? Leave me a comment!

--

--