Introduction
Redux is a predictable state container for JavaScript apps. If Redux is new to you, we recommend looking at our introduction to Redux.
In this article, you will learn how to persist user data using Redux in a React Native application. The application is a mock social network with a HomeScreen
displaying a count of connected friends, and a FriendsScreen
displaying a list of potential friends to add. You will use Redux to share the state between the two screens.
Prerequisites
To complete this tutorial, you’ll need:
A local development environment for Node.js. Follow How to Install Node.js and Create a Local Development Environment.
Familiarity with setting up your environment to create a new React Native project and using the iOS or Android simulators may be beneficial.
This tutorial builds on the topics covered in How To Use Routing with React Navigation in React Native. It is recommended that you read this tutorial for more context around how the project works, but is not required.
This tutorial was verified with Node v14.7.0, npm
v6.14.7, react
v16.13.1, react-native
v0.63.2, @react-navigation/native
v5.7.3, @react-navigation/stack
v5.9.0, redux
v4.0.5, and react-redux
v7.2.1.
Step 1 — Setting Up the Project and Installing Redux
This tutorial will use a modified version of the code in How To Use Routing with React Navigation in React Native. To get started, clone MySocialNetwork
:
git clone https://github.com/do-community/MySocialNetwork.git
Then, navigate to the project directory:
cd MySocialNetwork
Change the git branch to redux-starter
:
git checkout redux-starter
Next, install the project dependencies:
npm install
Then, install the redux
and react-redux
libraries in the project:
npm install redux@4.0.5 react-redux@7.2.1
Your project is now set up and your dependencies have been installed.
Step 2 — Creating a Reducer
To connect Redux to your app, you will need to create a reducer and an action.
First, you will create a friends reducer. A reducer is a pure function that takes the previous state and an action as arguments and returns a new state. The reducer is instrumental in keeping the current state of friends updated throughout the app as it changes.
Create the FriendsReducer.js
file at the root level of the project:
nano FriendsReducer.js
Add the following code:
FriendsReducer.js
import { combineReducers } from 'redux';
const INITIAL_STATE = {
current: [],
possible: [
'Alice',
'Bob',
'Sammy',
],
};
const friendsReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
default:
return state
}
};
export default combineReducers({
friends: friendsReducer
});
In this file, you create an INITIAL_STATE
variable with possible friends to add to your social network. Then you are exporting friendsReducer
as a property called friends
.
With your reducer in place, you will need a way to add friends.
Step 3 — Creating an Action
Actions are JavaScript objects that represent payloads of information that send data from your application to the Redux
store.
Actions have a type and an optional payload. In this tutorial, the type will be ADD_FRIEND
, and the payload will be the array index of a friend you are adding into the current
friends array.
Create the FriendsActions.js
file at the root level of the project:
nano FriendsActions.js
Add addFriend
:
FriendsActions.js
export const addFriend = friendsIndex => (
{
type: 'ADD_FRIEND',
payload: friendsIndex,
}
);
When a user clicks on a friend, this code will retrieve the friendsIndex
from the friends.possible
array. Now you will need to use that index to move this friend into the friends.current
array.
Revisit FriendsReducer.js
:
nano FriendsReducer.js
Add ADD_FRIEND
:
FriendsReducer.js
// ...
const friendsReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case 'ADD_FRIEND':
// Pulls current and possible out of previous state
// We do not want to alter state directly in case
// another action is altering it at the same time
const {
current,
possible,
} = state;
// Pull friend out of friends.possible
// Note that action.payload === friendIndex
const addedFriend = possible.splice(action.payload, 1);
// And put friend in friends.current
current.push(addedFriend);
// Finally, update the redux state
const newState = { current, possible };
return newState;
default:
return state
}
};
// ...
This code pulls the current and possible friends out of the previous state. Array.splice()
retrieves the friend from the array of possible friends. Array.push
adds the friend to array of current friends. After ther changes are made, the state is updated.
Now, you have a reducer and an action. You will need to apply the reducer to your app.
Step 4 — Adding the Reducer to the App
You will need to provide the friends
state of your app using React Redux’s Provider
component.
Open App.js
:
nano App.js
Import Provider
, createStore
, and friendsReducer
:
App.js
import 'react-native-gesture-handler';
import React from 'react';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import { StyleSheet } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import friendsReducer from './FriendsReducer';
import HomeScreen from './HomeScreen';
import FriendsScreen from './FriendsScreen';
// ...
Add and replace the highlighted code with createStore
and Provider
:
App.js
// ...
const store = createStore(friendsReducer);
class App extends React.Component {
// ...
render() {
return (
<Provider store={store}>
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Home"
component={HomeScreen}
/>
<Stack.Screen
name="Friends"
component={FriendsScreen}
/>
</Stack.Navigator>
</NavigationContainer>
</Provider>
)
}
}
Now friends
are accessible within your app, but you still need to add them to the HomeScreen
and FriendsScreen
.
Step 5 — Adding Redux to the Screens
In this step, you will make friends
accessible to your screens with the mapStateToProps
function. This function maps the state
from the FriendsReducer
to the props
in the two screens.
Let’s start with HomeScreen.js
. Open the HomeScreen.js
file:
nano HomeScreen.js
Add and replace the highlighted lines of code in HomeScreen.js
:
HomeScreen.js
import React from 'react';
import { connect } from 'react-redux';
import { StyleSheet, Text, View, Button } from 'react-native';
class HomeScreen extends React.Component {
render() {
return (
<View style={styles.container}>
<Text>You have (undefined) friends.</Text>
<Button
title="Add some friends"
onPress={() =>
this.props.navigation.navigate('Friends')
}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
const mapStateToProps = (state) => {
const { friends } = state
return { friends }
};
export default connect(mapStateToProps)(HomeScreen);
This code change adds react-redux
and makes friends
available to the HomeScreen
.
Next, add values for current friends (this.props.friends.current
):
HomeScreen.js
class HomeScreen extends React.Component {
render() {
return (
<View style={styles.container}>
<Text>You have { this.props.friends.current.length } friends.</Text>
<Button
title="Add some friends"
onPress={() =>
this.props.navigation.navigate('Friends')
}
/>
</View>
);
}
}
Your HomeScreen
will now display the number of current friends. You can now move on to the FriendsScreen
.
Open FriendsScreen.js
:
nano FriendsScreen.js
Add and replace the highlighted lines of code in FriendsScreen.js
:
FriendsScreen.js
import React from 'react';
import { connect } from 'react-redux';
import { StyleSheet, Text, View, Button } from 'react-native';
class FriendsScreen extends React.Component {
render() {
return (
<View style={styles.container}>
<Text>Add friends here!</Text>
<Button
title="Back to home"
onPress={() =>
this.props.navigation.navigate('Home')
}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
const mapStateToProps = (state) => {
const { friends } = state
return { friends }
};
export default connect(mapStateToProps)(FriendsScreen);
This code change adds react-redux
and makes friends
available to the FriendsScreen
.
Add values for possible friends (props.friends.possible
):
class FriendsScreen extends React.Component {
render() {
return (
<View style={styles.container}>
<Text>Add friends here!</Text>
{
this.props.friends.possible.map((friend, index) => (
<Button
key={ friend }
title={ `Add ${ friend }` }
/>
))
}
<Button
title="Back to home"
onPress={() =>
this.props.navigation.navigate('Home')
}
/>
</View>
);
}
}
Now when you navigate to the FriendsScreen
, you will see all possible friends from the reducer.
Finally, add the new Redux addFriend
action to FriendsScreen.js
:
FriendsScreen.js
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { StyleSheet, Text, View, Button } from 'react-native';
import { addFriend } from './FriendsActions';
class FriendsScreen extends React.Component {
render() {
return (
<View style={styles.container}>
<Text>Add friends here!</Text>
{
this.props.friends.possible.map((friend, index) => (
<Button
key={ friend }
title={ `Add ${ friend }` }
onPress={() =>
this.props.addFriend(index)
}
/>
))
}
<Button
title="Back to home"
onPress={() =>
this.props.navigation.navigate('Home')
}
/>
</View>
);
}
}
// ...
const mapDispatchToProps = dispatch => (
bindActionCreators({
addFriend,
}, dispatch)
);
export default connect(mapStateToProps, mapDispatchToProps)(FriendsScreen);
Let’s add two friends to the social network and navigate back to HomeScreen
to see how many current friends the user has:
With that, you have moved all the logic from App.js
into Redux
, which makes your app much more flexible, especially as you add more pages and features like authentication and database integration.
Before we wrap up, let’s clean up the code.
Step 6 — Cleaning Up
Now that you are using Redux
, you will no longer need the props you were passing from App.js
.
You can take cleaning up a step further by storing your action
types in a separate file.
You are using the string 'ADD_FRIEND'
in two places: in the action
and the friends reducer
. This is dangerous, because if you change the string in one place and not the other you could break your application. As your app grows, it makes sense to keep all these action
types in a file called types.js
.
Create the types.js
file in the root level:
nano types.js
Add the following code:
types.js
export const ADD_FRIEND = 'ADD_FRIEND';
Then, revisit FriendsActions.js
to use the new ADD_FRIEND
:
nano FriendsActions.js
Change the quoted 'ADD_FRIEND'
to the variable ADD_FRIEND
in your action
:
FriendsActions.js
import { ADD_FRIEND } from './types';
export const addFriend = friendsIndex => (
{
type: ADD_FRIEND,
payload: friendsIndex,
}
);
Then, revisit FriendsReducer.js
to also use the new ADD_FRIEND
:
nano FriendsReducer.js
Change the quoted 'ADD_FRIEND'
to the variable ADD_FRIEND
in your reducer
:
FriendsReducer.js
import { combineReducers } from 'redux';
import { ADD_FRIEND } from './types';
// ...
const friendsReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case ADD_FRIEND:
// ...
default:
return state;
}
};
This makes the application less fragile. When developing your applications, you should be aware of opportunites for consolidating code and avoiding repeating yourself.
Conclusion
In this tutorial, you covered redux
, reducers
, actions
, and scalable data management.
There are a lot more things you can do with Redux, from keeping data in sync with a database, to authentication and keeping track of user permissions.
The complete source code for this tutorial is available on GitHub.
If you’d like to learn more about React, take a look at our How To Code in React.js series, or check out our React topic page for exercises and programming projects.