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?
A. NPM Dependencies Explained:
- 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)
B. Install Dependencies:
npm install redux react-redux redux-persist --save
C. Install Dependencies (Development):
npm install redux-logger --save-dev
3. Example App + Code
Github Repo: https://github.com/pavlealeksic/redux-persist-demo
A. App Overview
The App will have two Reducers: authReducer 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).
B. App Screenshot
C. App File Structure
This example will be using 8 files:
- App.js (React Native App)
- Counter.js (Counter Screen)
- store.js (Redux Store)
- index.js (Redux Root Reducer)
- authActions.js (Authentication Actions)
- counterActions.js (Counter Actions)
- authReducer.js (Redux Auth Reducer)
- counterReducer.js (Redux Counter Reducer)
D. App Files
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.