React Native Publishing an Android App

React Native + Redux: Why You Should Be Using Redux-Persist To Save Your Redux Store From Disappearing

Notes:

  • This guide assumes you know React Native and Redux.

1. What Is Redux-Persist?

Say you’re using your app and at some point, you decide to clear out all of your apps because you have too many open. You then click on your app icon and it launches, but you’re now prompted with a Login screen.

You didn’t press log out, so why did you get logged out? It’s because the state isn’t being saved when the app is closed. Redux-Persist saves the Redux Store when the app is closed or refreshed in Expo.

2. How Do We Use Redux Persist?

  • redux (Global Store) (NPM Link)
  • react-redux (For React/React Native) (NPM Link)
  • redux-persist (Persists Redux Global Store) (NPM Link)
  • redux-logger (Logs Redux State for when developing) (NPM Link)
npm install redux react-redux redux-persist --save
npm install redux-logger --save-dev

3. Example App + Code

Github Repo: https://github.com/pavlealeksic/redux-persist-demo

The App will have two ReducersauthReducer and counterReducer. In our store.js file, we can Blacklist or Whitelist reducers to persist data from only specific reducers. In our case, we are only going to persist the data from the authReducer.

To test that Redux Persist is working in our app, we can click the login button (Status should change to true) and increase the counter to 5. When we refresh our app, the Logged In status should remain true (Whitelisted) and the counter should be reset to 0 (Blacklisted).

React Native + Redux: Why You Should Be Using Redux-Persist To Save Your Redux Store From Disappearing
App Screenshot

This example will be using 8 files:

  1. App.js (React Native App)
  2. Counter.js (Counter Screen)
  3. store.js (Redux Store)
  4. index.js (Redux Root Reducer)
  5. authActions.js (Authentication Actions)
  6. counterActions.js (Counter Actions)
  7. authReducer.js (Redux Auth Reducer)
  8. counterReducer.js (Redux Counter Reducer)

App.js

// Imports: Dependencies
import React from 'react';
import { PersistGate } from 'redux-persist/integration/react';
import { Provider } from 'react-redux';// Imports: Screens
import Counter from './screens/Counter';// Imports: Redux Persist Persister
import { store, persistor } from './store/store';// React Native: App
export default function App() {
  return (
    // Redux: Global Store
    <Provider store={store}>
      <PersistGate loading={null} persistor={persistor}>
        <Counter />
      </PersistGate>
    </Provider>
  );
};

Counter.js

// Imports: Dependencies
import React, { Component } from 'react';
import { Button, Dimensions, SafeAreaView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { connect } from 'react-redux';// Screen Dimensions
const { height, width } = Dimensions.get('window');// Screen: Counter
class Counter extends React.Component {
  render() {
    return (
      <SafeAreaView style={styles.container}>
        <View style={styles.loggedInContainer}>
          <Text style={styles.loggedInText}>Logged In: </Text>
          <Text style={styles.loggedInText}>{`${this.props.loggedIn}`}</Text>          <Button
            title="Login"
            onPress={this.props.loggedIn === false ? () => this.props.reduxLogin(true) : () => this.props.reduxLogin(false)}
            style={styles.loginButton}
          />
        </View>        <Text style={styles.counterTitle}>Counter</Text>        <View style={styles.counterContainer}>
          <TouchableOpacity onPress={() => this.props.reduxIncreaseCounter()}>
            <Text style={styles.buttonText}>+</Text>
          </TouchableOpacity>          <Text style={styles.counterText}>{this.props.counter}</Text          <TouchableOpacity onPress={() => this.props.reduxDecreaseCounter()}>
            <Text style={styles.buttonText}>-</Text>
          </TouchableOpacity>
        </View>
      </SafeAreaView>
    )
  }
}// Styles
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
   },
   loggedInContainer: {
     display: 'flex',
     flexDirection: 'column',
     justifyContent: 'center',
     alignItems: 'center',
     marginBottom: 40,
   },
   loginButton: {
     marginTop: 20,
     paddingTop: 20,
   },
   counterContainer: {
     display: 'flex',
     flexDirection: 'row',
     justifyContent: 'center',
     alignItems: 'center',
   },
   loggedInText: {
     fontFamily: 'System',
     fontSize: 17,
     fontWeight: '400',
     color: '#000',
   },
   counterTitle: {
     fontFamily: 'System',
     fontSize: 32,
     fontWeight: '700',
     color: '#000',
   },
   counterText: {
     fontFamily: 'System',
     fontSize: 36,
     fontWeight: '400',
     color: '#000',
   },
   buttonText: {
     fontFamily: 'System',
     fontSize: 50,
     fontWeight: '300',
     color: '#007AFF',
     marginLeft: 40,
     marginRight: 40,
   },
});// Map State To Props (Redux Store Passes State To Component)
const mapStateToProps = (state) => {
  // Redux Store --> Component
  return {
    counter: state.counterReducer.counter,
    loggedIn: state.authReducer.loggedIn,
  };
};// Map Dispatch To Props (Dispatch Actions To Reducers. Reducers Then Modify The Data And Assign It To Your Props)
const mapDispatchToProps = (dispatch) => {
  // Action
  return {
    // Increase Counter
    reduxIncreaseCounter: () => dispatch(increaseCounter()),
    // Decrease Counter
    reduxDecreaseCounter: () => dispatch(decreaseCounter()),
    // Login
    reduxLogin: (trueFalse) => dispatch(login(trueFalse)),
  };
};// Exports
export default connect(mapStateToProps, mapDispatchToProps)(Counter);

store.js

// Imports: Dependencies
import AsyncStorage from '@react-native-community/async-storage';
import { createStore, applyMiddleware } from 'redux';
import { createLogger } from 'redux-logger';
import { persistStore, persistReducer } from 'redux-persist';// Imports: Redux
import rootReducer from '../reducers/index';// Middleware: Redux Persist Config
const persistConfig = {
  // Root
  key: 'root',
  // Storage Method (React Native)
  storage: AsyncStorage,
  // Whitelist (Save Specific Reducers)
  whitelist: [
    'authReducer',
  ],
  // Blacklist (Don't Save Specific Reducers)
  blacklist: [
    'counterReducer',
  ],
};// Middleware: Redux Persist Persisted Reducer
const persistedReducer = persistReducer(persistConfig, rootReducer);// Redux: Store
const store = createStore(
  persistedReducer,
  applyMiddleware(
    createLogger(),
  ),
);// Middleware: Redux Persist Persister
let persistor = persistStore(store);// Exports
export {
  store,
  persistor,
};

index.js

// Imports: Dependencies
import { combineReducers } from 'redux';// Imports: Reducers
import authReducer from './authReducer';
import counterReducer from './counterReducer';

// Redux: Root Reducer
const rootReducer = combineReducers({
  authReducer: authReducer,
  counterReducer: counterReducer,
});// Exports
export default rootReducer;

authActions.js

// Login
export const login = (trueFalse) => ({
  type: 'LOGIN',
  trueFalse: trueFalse,
});

counterActions.js

// Increase Counter
export const increaseCounter = () => ({
  type: 'INCREASE_COUNTER',
});// Decrease Counter
export const decreaseCounter = () => ({
  type: 'DECREASE_COUNTER',
});

authReducer.js

// Initial State
const initialState = {
  loggedIn: false,
};// Reducers (Modifies The State And Returns A New State)
const authReducer = (state = initialState, action) => {
  switch (action.type) {    // Logged In
    case 'LOGIN': {
      return {
        // State
        ...state,
        // Redux Store
        email: action.trueFalse,
      }
    }    // Default
    default: {
      return state;
    }
  }
};// Exports
export default authReducer;

counterReducer.js

// Initial State
const initialState = {
  counter: 0,
};// Reducers (Modifies The State And Returns A New State)
const counterReducer = (state = initialState, action) => {
  switch (action.type) {// Increase Counter
    case 'INCREASE_COUNTER': {
      return {
        // State
        ...state,
        // Redux Store
        counter: state.counter + 1,
      }
    }// Decrease Counter
    case 'DECREASE_COUNTER': {
      return {
        // State
        ...state,
        // Redux Store
        counter: state.counter - 1,
      }
    }    // Default
    default: {
      return state;
    }
  }
};// Exports
export default counterReducer;

Keep Persisting

And that’s it! Redux Persist is now working in your app and your Redux Store can now be saved on refresh.

No one’s perfect. If you’ve found any errors, want to suggest enhancements, or expand on a topic, please feel free to send me a message. I will be sure to include any enhancements or correct any issues.