Sometimes, a developer needs to persist information locally for a user. For example, let’s say you built an ecommerce platform where users can add items to their cart without signing in. You need to implement a way for the app to remember what items the user added to the cart. It’s a bad experience for the user to close the app for a while, only to find that the items they added to the cart are no longer there.
Another scenario would be when after a user signed into your app, they closed the app for a minute, only to be taken to the login page to log in again.
In this article, we’ll learn how to use Redux Persist to persist the Redux store. First, we’ll build a small to-do application and set up its state management with Redux. Then, we’ll set up the Redux Persist library for data preservation in scenarios where the app is closed. Let’s get started!
In React Native applications, data can be persisted locally using AsyncStorage
. AsyncStorage
is an asynchronous, persistent, key-value storage system that is global to the entire app.
Redux Persist is a tool used to seamlessly save the application’s Redux state object to AsyncStorage
. On app launch, Redux Persist retrieves this persisted state and saves it back to Redux.
Normally, Redux sets the initial state of your app upon launch. Soon after, Redux Persist fetches your persisted state, overriding any initial state in a process the Redux Persist team calls rehydration.
Redux is an open source JavaScript library for managing and centralizing application state. It maintains the state of an entire application in a single immutable state object, which can’t be changed directly. When something changes, a new object is created using actions and reducers.
In this tutorial, we’ll demonstrate implementing Redux Persist on the Expo snack platform.
Let’s create a folder called screens
. Inside it, create a file called Todo.js
, which will contain the input field to add to-dos and also display the to-dos added:
import React from 'react'; import { StyleSheet, Text, View } from 'react-native'; export default function BooksListApp() { return ( <View style={styles.container}> <Text style={styles.paragraph}>React Native ToDo App with Redux Persist </Text> <Text style={styles.title}> Add ToDo Here</Text> <TextInput style={styles.input} mode="outlined" label="Task" value={task} onChangeText={task => setTask(task)} /> <Button title='Add' color="#841584" /> <FlatList data={todos} keyExtractor={(item) => item.id} renderItem={({item, index}) => { return ( <Text style={styles.list}>{item.task}</Text> ); }} /> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, paddingTop: Constants.statusBarHeight, backgroundColor: '#ecf0f1', padding: 10, }, paragraph: { margin: 24, fontSize: 18, fontWeight: 'bold', textAlign: 'center', }, title: { margin: 10, fontSize: 16, fontWeight: 'bold', textAlign: 'center', }, list: { fontSize: 18, fontWeight: 'bold', textAlign: 'center', }, input: { height: 40, margin: 12, borderWidth: 1, padding: 10, }, });
To set up Redux, first install it with the following code:
npm install redux
With Redux, the state of the whole application is managed by one JavaScript object. To make changes to that object, we can dispatch actions.
Let’s create another folder called redux
for state management. Inside it, let’s create an action.js
file to set up the action types and action creators:
export const ADD_TODO = "ADD_TODO"; let todoId = 0; export const addTodo = task => ({ type: ADD_TODO, payload: { id: ++todoId, task } });
The action type ADD_TODO
is self-explanatory. We call it when we need to update the state by adding a new todo.
When an action has been dispatched, a reducer updates the state by making the changes to the state object. The reducer is a pure function that takes two inputs, the state and action, and must return the default state.
Let’s create another file in the redux
folder and import the action type we created earlier. We’ll also create an initial app state:
import { ADD_TODO } from "./action"; const initialState = { todos: [] }; const todoReducer = (state = initialState, action) => { switch (action.type) { case ADD_TODO: { const { id, task } = action.payload return { ...state, todos: [ ...state.todos, { id, task }] }; } default: return state; } } export default todoReducer;
In the code above, we defined a reducer function, todoReducer
, which takes initialState
as the default value for the first argument and action
as the second argument.
The Redux store holds the state at the application level so that it can be accessed from any component. The Redux store is an object that brings actions and reducers together.
We’ll use the createStore
method from Redux to configure a Redux store. It takes the reducer as an argument.
Create a store.js
file inside the redux
folder and initialize the Redux store as follows:
import { createStore } from "redux"; import todoReducer from './reducers'; export default createStore(todoReducer);
Next, we’ll make the Redux store globally available by wrapping the entire app in a higher order component called Provider
and passing the store to it.
In the App.js
file, import Provider
from React Redux and wrap the app in it:
import store from './redux/store'; import { Provider } from 'react-redux'; const App = () => { return ( <Provider store={store}> <MyTodo/> </Provider> ); } export default App;
In the code above, we have made a connection between the React components and the Redux store and state. Therefore, any component in this application can access the app’s state at any point.
With Redux set up, let’s write a function that will dispatch an action when a user adds a to-do item. In Todo.js
, add the code below:
import { useSelector, useDispatch } from 'react-redux'; import { addTodo } from './redux/action'; const [task, setTask] = React.useState(''); const todoList = useSelector(state => state.todos); const dispatch = useDispatch(); const handleAddTodo = () => { dispatch(addTodo(task)) setTask('') }
We imported the useSelector
Hook from React Redux for getting the state in this component and useDispatch
for dispatching an action. We then call the function when the Add
button is clicked:
<Button title='Add' color="#841584" onPress={handleAddTodo} />
Now, when we add an item in the input field and click the button, the addTodo
action creator is called, which, in turn, dispatches the ADD_TODO
action to the reducer so that the item is added and the app’s state is updated.
At this point, you’ll notice that the reducer is working, and we have our to-do app up and running. But, you’ll notice that when we close the application for a while and open it again, all the items we added are gone, indicating that the Redux state has been reset to initial state.
We can persist the items we added using Redux Persist. Therefore, even when we close the application and open it again, we don’t lose our to-do items.
Let’s install the Redux Persist package and the React Native AsyncStorage
package with the following command:
npm i redux-persist @react-native-async-storage/async-storage
We’ll import persistStore, persistReducer
from Redux Persist and add the following configuration:
import AsyncStorage from '@react-native-async-storage/async-storage'; import { persistStore, persistReducer } from 'redux-persist' import storage from 'redux-persist/lib/storage' const persistConfig = { key: 'root', storage: AsyncStorage, }
To create the configuration for React Persist, you pass the key and the storage engine, which are required. There are other optional configurations like whitelist
, blacklist
, version
, stateReconciler
, and debug
. If you don’t want to persist a part of your state, you could put it in the blacklist
. Alternately, the whitelist
defines the parts of the state you wish to persist.
Now, we’ll call the persistReducer
, passing in the config object and our todoReducer
.
We also export the persistor
, which is an object that is returned by persistStore
that wraps the original store:
const persistedReducer = persistReducer(persistConfig, todoReducer) export const store = createStore(persistedReducer) export const persistor = persistStore(store)
At the end of the configuration, our store.js
will look like the following code:
import AsyncStorage from '@react-native-async-storage/async-storage'; import { persistStore, persistReducer } from 'redux-persist' import todoReducer from './reducers'; import { createStore } from 'redux' import storage from 'redux-persist/lib/storage' const persistConfig = { key: 'root', storage: AsyncStorage, } const persistedReducer = persistReducer(persistConfig, todoReducer) export const store = createStore(persistedReducer) export const persistor = persistStore(store)
Then, we update App.js
by importing PersistGate
and wrapping the app in it:
import * as React from 'react'; import { Text } from 'react-native'; import { PersistGate } from 'redux-persist/integration/react' import MyTodo from './Todo'; import {store, persistor} from './redux/store'; import { Provider } from 'react-redux'; const App = () => { return ( <Provider store={store}> <PersistGate loading={<Text>Loading...</Text>} persistor={persistor}> <MyTodo/> </PersistGate> </Provider> ); } export default App;
Redux Persist persists the Redux store for us in the AsyncStorage
. Even when we leave the app and come back later, it fetches the data from AsyncStorage
and initializes the Redux store with it.
In this article, we reviewed what Redux Persist is and how to set it up in a React Native application. Redux Persist is helpful when you need to persist login sessions and other information without a database. It greatly improves the user experience by allowing your users to exit and return to your application without losing their data. I hope you enjoyed this article, and be sure to leave a comment if you have any questions. Happy coding!
LogRocket is a React Native monitoring solution that helps you reproduce issues instantly, prioritize bugs, and understand performance in your React Native apps.
LogRocket also helps you increase conversion rates and product usage by showing you exactly how users are interacting with your app. LogRocket's product analytics features surface the reasons why users don't complete a particular flow or don't adopt a new feature.
Start proactively monitoring your React Native apps — try LogRocket for free.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowBuild scalable admin dashboards with Filament and Laravel using Form Builder, Notifications, and Actions for clean, interactive panels.
Break down the parts of a URL and explore APIs for working with them in JavaScript, parsing them, building query strings, checking their validity, etc.
In this guide, explore lazy loading and error loading as two techniques for fetching data in React apps.
Deno is a popular JavaScript runtime, and it recently launched version 2.0 with several new features, bug fixes, and improvements […]