Introduction to Using Redux in a React Native App

Zeb Girouard

In this article, we will learn how to persist user data using Redux in a React Native application.

Redux is a predictable state container for JavaScript apps. If Redux is new to you, we recommend looking at this intro of ours.

As you'll see, Redux is used in the exact same way in React Native as its used in React. This way, if you're already familiar with React and Redux, then you already know what you need to know and you might not need this article. 😉

MyAlligatorFace Demo App

Here at Alligator.io, we want to help people stay connected, and to that end, we are debuting a social networking app called MyAlligatorFace 🐊.

In Part 1 of this React Native walkthrough, we started the app with two navigation screens, Friends and Home and looked at React Navigation.

If you would like to dive straight into Redux with React Native, pull down the starter code from here on Github. Take a moment to familiarize yourself with App.js, Home.js, Friends.js, and AppNavigator.js.

We have two screens in MyAlligatorFace: on the Home page, we see how many friends we have and on the Friends page we can add new friends. Currently, we are storing this array of friends in the local state of App.js. As our app grows, we will need more control in persisting this data.

For instance, some pages will not need to access friends. Other pages will need to access friends, but not the addFriends function.

Luckily, this flexibility and control is precisely what Redux is designed for.

Getting Started with Redux

Before we add Redux to our project, we need to spin up our current version of MyAlligatorFace.

Run the following commands in your Terminal:

$ git clone https://github.com/alligatorio/MyAlligatorFace.git --branch navigation
$ cd MyAlligatorFace
$ git checkout navigation
$ git checkout -b redux
$ npm start

In our React Native simulator, we see a Home and Friends page that we can navigate between.

Next, install the redux library in the project:

$ npm install redux react-redux

To connect Redux to our app, we need to create a reducer and an action. First, we will create a friends reducer.

Our 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 our app as it changes.

Create the FriendReducer.js file at the root level of the project:

FriendReducer.js

import { combineReducers } from 'redux';

const INITIAL_STATE = {
  current: [],
  possible: [
    'Allie',
    'Gator',
    'Lizzie',
    'Reptar',
  ],
};

const friendReducer = (state = INITIAL_STATE, action) => {
  switch (action.type) {
    default:
      return state
  }
};

export default combineReducers({
  friends: friendReducer,
});

In this file we are creating an INITIAL_STATE variable with 4 possible friends to add to our social network. Then we export friendReducer as a property called friends.

Adding the Reducer to Our App

Now we need to provide this friends state to our app using React Redux’s Provider component. Add the highlighted code to the App.js file:

FriendReducer.js

import React from 'react';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import friendReducer from './FriendReducer';
import AppNavigator from './AppNavigator';

const store = createStore(friendReducer);

export default class App extends React.Component {
  constructor(props) {
    super(props)
    // ...
  }

  addFriend = (index) => {
    // ...
  }

  render() {
    return (
      <Provider store={ store }>
        <AppNavigator
          screenProps={ {
            currentFriends: this.state.currentFriends,
            possibleFriends: this.state.possibleFriends,
            addFriend: this.addFriend,
          } }
        />
      </Provider>
    );
  }
}

Now friends are accessible in our app, but we need to add them to our Friends and Home screens.

Adding Redux to our Screens

We make friends accessible to our screens with the mapStateToProps function. As the name implies, it maps the state from our FriendReducer to the props in our two screens.

Add the highlighted code to Home.js and Friends.js:

Home.js


import React from 'react';
import { StyleSheet, Text, View, Button } from 'react-native';
import { connect } from 'react-redux';

class Home extends React.Component {
  render() {
    return (
      <View style={styles.container}>
        We have { this.props.screenProps.currentFriends.length } friends!
        <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)(Home);

Friends.js


import React from 'react';
import { StyleSheet, Text, View, Button } from 'react-native';
import { connect } from 'react-redux';

class Friends extends React.Component {
  render() {
    return (
      <View style={styles.container}>
        Add friends here!
        {
          this.props.screenProps.possibleFriends.map((friend, index) => (
            <Button
              key={ friend }
              title={ `Add ${ friend }` }
              onPress={() =>
                this.props.screenProps.addFriend(index)
              }
            />
          )
        )
        }
        <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)(Friends);

Finally, swap out the screenProps from App.js with the props from Redux.

Home.js


import React from 'react';
import { StyleSheet, Text, View, Button } from 'react-native';
import { connect } from 'react-redux';

class Home extends React.Component {
  render() {
    return (
      <View style={styles.container}>
        We have { this.props.friends.current.length } friends!
        <Button
          title="Add some friends"
          onPress={() =>
            this.props.navigation.navigate('Friends')
          }
        />
      </View>
    );
  }
}

// ...

Friends.js


import React from 'react';
import { StyleSheet, Text, View, Button } from 'react-native';
import { connect } from 'react-redux';

class Friends extends React.Component {
  render() {
    return (
      <View style={styles.container}>
        Add friends here!
        {
          this.props.friends.possible.map((friend, index) => (
            <Button
              key={ friend }
              title={ `Add ${ friend }` }
              onPress={() =>
                this.props.screenProps.addFriend(index)
              }
            />
          )
        )
        }
        <Button
          title="Back to home"
          onPress={() =>
            this.props.navigation.navigate('Home')
          }
        />
      </View>
    );
  }
}

// ...

Now, when we navigate to the Friends screen, we see all four friend possibilities from our reducer, instead of the three from the component state we initially had in App.js:


Four friends


Now that we have our reducer, let’s create an action to add friends.

Our Action

Actions are JavaScript objects that represent payloads of information that send data from your application to your Redux store.

Actions have a type and an optional payload. In our case, the type will be ADD_FRIEND, and the payload will be the array index of a friend we are adding into our current friends array.

Create the FriendActions.js file at the root level of the project:

FriendActions.js

export const addFriend = friendIndex => (
  {
    type: 'ADD_FRIEND',
    payload: friendIndex,
  }
);

When we click on a friend, we pull the friendIndex from the friends.possible array. Now we need to use that index to move this friend into the friends.current array.

We do this in FriendReducer.js:

FriendReducer.js

import { combineReducers } from 'redux';

// ...

const friendReducer = (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 our redux state
      const newState = { current, possible };
      return newState;
    default:
      return state;
  }
};

export default combineReducers({
	friends: friendReducer,
});

Finally, replace the current addFriend function in Friends.js with our Redux action:

Friends.js


import React from 'react';
import { StyleSheet, Text, View, Button } from 'react-native';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { addFriend } from './FriendActions';

class Friends extends React.Component {
  render() {
    return (
      <View style={styles.container}>
        Add friends here!
        {
          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)(Friends);

Let’s add “Gator” and “Reptar” to our friend network and navigate back to Home to see how many friends we have.


Two friends now


We did it! We moved all the logic from App.js into Redux, which makes our app much more flexible, especially as we add more pages and features like authentication and database integration.

Before we wrap up, we need to clean up our code.

Cleaning Up

Now that we are using Redux, we no longer need the props we are passing from App.js. If we take out the redundant code, we are left with the code below:

App.js


import React from 'react';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import friendReducer from './FriendReducer';
import AppNavigator from './AppNavigator';

const store = createStore(friendReducer);

export default class App extends React.Component {
  render() {
    return (
      <Provider store={ store } >
        <AppNavigator />
      </Provider>
    );
  }
}

We can take our cleanup a step further.

We are using the string 'ADD_FRIEND' in two places: in the action and the friends reducer. This is dangerous, because if we change the string in one place and not the other we could break our application. As our app grows, it makes sense to keep all these action types in a file called types.js.

Create that file in the root level:

types.js

export const ADD_FRIEND = 'ADD_FRIEND'

Change the quoted ‘ADD_FRIEND’ to the variable ADD_FRIEND in our action and reducer:

FriendActions.js

import { ADD_FRIEND } from './types';

export const addFriend = friendIndex => (
  {
    type: ADD_FRIEND,
    payload: friendIndex,
  }
);

FriendActions.js

import { combineReducers } from 'redux';
import { ADD_FRIEND } from './types';

// ...

const friendReducer = (state = INITIAL_STATE, action) => {
  switch (action.type) {
    case ADD_FRIEND:
      // ...
    default:
      return state;
  }
};

// ...

Conclusion

In addition to creating the newest and hottest social media app, MyAlligatorFace, we also covered the following in this article:

  • redux
  • react-redux
  • reducers
  • actions
  • 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. Stay tuned for future posts where we will explore these concepts.

  Tweet It

🕵 Search Results

🔎 Searching...

Sponsored by #native_company# — Learn More
#native_title# #native_desc#
#native_cta#