Simple Redux Persist configuration in React Native / Expo

Simple Redux Persist configuration in React Native / Expo

Intro

I have been working with react-persist in some React-Redux-Electron projects in the past, and today I decided to add redux-persist into my new Expo based react-native app.

I must say that, if well its simple enough, I always get lost in their docs. And last year I realized that they do give you more features than they write in their page (I remember to have to dive into their code because I had to “hack” the way the information was stored in an Electron based program, and it was a pain to realize there were hardly info about some of the nice things they let you do).

Anway, I am writing this entry a bit as a reference for me but just in case some other might find it useful.

Redux and Persistence

First a bit of memo. Redux is a javascript library that you can use to store the state of any javascript based webapp you migth want to do. Some people I know were mislead into believing that redux is a part of React, but it is not. However, React and Redux interact very well. As I started using Redux in a non React environment, when I started using it in Redux, I realized how convenient were some of the features that it bring to the table (specifically relating with state subscriptions).

However, Redux doesn’t have any persistence method. When you close the app/website, the info is lost. Luckily you can find things like redux-persist, that let you add a persistence layer to your Redux state.

Redux Persist

Redux Persist is quite a convenient tool, because after it has been configured, it takes charge to all the persistence needs you might have. It gets the redux data and persist it periodically (by default it updates the persistence every time you change any of the reducers, however you can configure the way this persistence is done, debounce it, although this goes beyond to this blog entry objectives).

some concepts:

  • persist: save the store information so it can be recovered in other moment
  • rehydrate: recover the information stored so it can be upated in the redux.

Redux Persist in React-Native

There are some stuff to take in account before adding Redux Persist to your project:

  • You can persist all your reducers or to select specific ones that will be persisted
  • It does not persist by itself. You need to define an Storage Engine that will interact with something (cookie, db, etc) in order to keep the memory. Here we will use AsyncStorage
  • In the case of React and React-Native, you need to add the<PersistGate> component in order to use it properly.

Note: Because in their documentation they only define “React” where they were defining the PersistGate component, it confused me and I didn’t know if I had to add such component into my React-Native App.

Reducer original configuration

In my project I have the Redux configuration in app/store/index.js:

import { createStore }                  from "redux";
import { persistStore, persistReducer } from "redux-persist";import { AsyncStorage }                        from 'react-native'
import {rootReducer}                    from './reducer/index'import storeConstants                          from './constants'const store = createStore(rootReducer, storeConstants.DEF_STORE); const getStore = () => store;
const getState = () => {
    return store.getState();
};export { getStore,    getState,};
export default { getStore,    getState,}

Simple enough. As you can see I defined the rootReducer in another file (app/store/reducer/index).

import { combineReducers }   from 'redux';
import appStateReducer       from './appState/index';const rootReducer = combineReducers({
    appState: appStateReducer
});export {rootReducer};export default {rootReducer}

Also a simple rootReducer. It might grow in the future though.

Finally, App.js is as follows

import React         from 'react';
import {Provider}    from 'react-redux';
import {View}        from 'react-native';
import CConversor    from './app/index.js'
import {getStore}    from './app/store';export default class App extends React.Component {
    render() {
        const myStore = getStore();        
        return (
            <Provider store={ myStore }>                       
                <CConversor/>
            </Provider>        
        );    
    }
}

Modifying Redux Config

So first of all, we need to import the new stuff we will use:

import { persistStore, persistReducer } from "redux-persist";
import { AsyncStorage }                 from 'react-native'
  • persistReducer will be used in our rootReducer to define it as something we want to persist. It is possible to go inside the rootReducer and use persistReducer in some and leave others whitout persistence, but in this blog we will persist all the rootReducer
  • persistStore will be used in the store, after being created, and it will define that the store will use Redux-Persist
  • AsyncStorage is the Engine we are using now. In this case is the react-native default one. There are others, like the expo specific redux-persist-expo-securestore, that add a security layer into the persistence, but I don’t need it for my current project.

The next step is to create a configuration object for persistReducer.

const persistConfig = {
    key: "root",
    storage: AsyncStorage
};

persistConfig can have several keys:

  • key: this value will define what will be the key that we will use as identifier to save the persisted information. Once created must be the same always (if you change the name after there was a persistence, redux-persist won’t find any information and it will load the default values)
  • storage: The engine we will use for persistence.

Even if we don’t use them here, there exists other values that you can add to the config object. Some examples are:

  • blacklist: list of reducers names that will be ignored when the persistence will be done
  • whitelist: list of reducers names that will be used when the persistence will be done. Other reducers will be ignored
  • transforms: custom the way we persist and rehydrate the data
  • stateReconciler: the way that the information will be merged when its being rehydrated.

Next, we need to create the Store and the Persistor

const persistedReducer = persistReducer(persistConfig, rootReducer);
const store = createStore(persistedReducer,
                          storeConstants.DEF_STORE);
const persistor = persistStore(store);

In here we have used the persistConf already defined and the rootReducer in persistReducer. This will make a new reducer that we have used in the creation of the store.

Then the persistor is made using persistStore with the store we have created. The persistor will be used when defining the PersistGate in react.

So, after all this modifications, app/store.index.js looks as follows:

import { createStore } from "redux";
import { persistStore, persistReducer } from "redux-persist";
import { AsyncStorage } from 'react-native'
import {rootReducer}   from './reducer/index';
import storeConstants  from './constants'const persistConfig = {
    key: "root",    
    storage: AsyncStorage
};
const persistedReducer = persistReducer(persistConfig, rootReducer);
const store = createStore(persistedReducer,                 
                          storeConstants.DEF_STORE);
const persistor = persistStore(store);const getPersistor = () => persistor;
const getStore = () => store;
const getState = () => {
    return store.getState();
};export {
    getStore,
    getState,
    getPersistor
};export default {
    getStore,
    getState,
    getPersistor
}

In my project I use getters to access to the store and the persistors. I also added a getState shortcut, but, as I am using a React-Native-Redux environment, I tend to avoid using it to access to the state unless it is really necesary.

Modifying React

The last step to add the persistence layer to the project is to modify the App.js. In this ile we should add a special Component from redux-persist called PersistGate. This component will make sure that the store is rehydrated before react can access to it.

import React           from 'react';
import {Provider}      from 'react-redux';
import {View, ActivityIndicator} from 'react-native';
import CConversor      from './app/index.js'import {getStore, getPersistor}          from './app/store';
import { PersistGate } from 'redux-persist/integration/react'export default class App extends React.Component {
    renderLoading = () => {
        return (
            <View>                
                <ActivityIndicator size={"large"} />
            </View>        
        );    
    };    render() {
        const myStore = getStore();  
        const myPersistor = getPersistor();        return (
            <Provider store={ myStore }>                
                <PersistGate 
                    persistor={myPersistor} 
                    loading={this.renderLoading()}
                >
                   <CConversor/>                
                </PersistGate>            
          </Provider>        
        );
    }
}

Here we can see that PersistGate must be inside the Provider, so it can access to the store. Also, it has to have as a prop the persistor we made out of the store. PersistGate accepts an extra prop called “loading”, in where you can add a React component to show that the sistem is loading and is not ready yet.