by

Learn how to use Redux to store and manage the data in React Native apps.

What We Will Be Building

In this tutorial we’re going to connect two pieces of a puzzle together:

  1. Movie Tickets Booking App with React Native
  2. API Backend with Node.js, Express, and MongoDB

And update Movie Tickets Booking app to use API Backend to fetch the data instead of having it hardcoded in javascript files, and to use Redux to store and manage the data.

For your reference, the final code for the app we’re building can be found in this GitHub repo.

Let’s Get Started

Let’s start off by checking out the code from previous tutorial. Open Terminal App and execute these commands:

git clone --branch part-1 https://github.com/rationalappdev/MovieTickets.git;
cd MovieTickets;

Install Dependencies

First of all, let’s install a couple of new dependencies.

  • Redux to store and manage our data fetched from the API.
npm install --save redux react-redux
  • Babel plugin to use ES7 decorators.
npm install --save-dev babel-plugin-transform-decorators-legacy

Update .babelrc

Next, we need to update .babelrc to enable ES7 decorators.

  • Open .babelrc file and add transform-decorators-legacy plugin that we just installed:
{
  "presets": ["react-native"],
  "plugins": ["transform-decorators-legacy"]
}

Configure Redux

Next, let’s configure our Redux reducer and middleware.

  • Create a new file called redux.js within src folder and add a couple of things.

The first part is a middleware that is looking for GET_MOVIE_DATA action and calls the API when it’s dispatched.

Whenever you dispatch an action with Redux it goes through this middleware. And when GET_MOVIE_DATA is dispatched it dispatches GET_MOVIE_DATA_LOADING action to set loading flag to true in the storage and calls the API. And once it gets an API response it dispatches GET_MOVIE_DATA_RECEIVED action and passed the response data to it.

import { Platform } from 'react-native';

const API = Platform.OS === 'android'
  ? 'http://10.0.3.2:3000/v1' // works for Genymotion
  : 'http://localhost:3000/v1';

export const apiMiddleware = store => next => action => {
  // Pass all actions through by default
  next(action);
  switch (action.type) {
    // In case we receive an action to send an API request
    case 'GET_MOVIE_DATA':
      // Dispatch GET_MOVIE_DATA_LOADING to update loading state
      store.dispatch({type: 'GET_MOVIE_DATA_LOADING'});
      // Make API call and dispatch appropriate actions when done
      fetch(`${API}/movies.json`)
        .then(response => response.json())
        .then(data => next({
          type: 'GET_MOVIE_DATA_RECEIVED',
          data
        }))
        .catch(error => next({
          type: 'GET_MOVIE_DATA_ERROR',
          error
        }));
      break;
    // Do nothing if the action does not interest us
    default:
      break;
  }
};

And the second part is a reducer that handles dispatched actions and updates the storage. When apiMiddleware dispatches GET_MOVIE_DATA_RECEIVED this reducer is being called, and it pulls movie data out of passed response data and stores it in the storage. And then we can use this data in other components.

export const reducer = (state = { movies: [], loading: true }, action) => {
  switch (action.type) {
    case 'GET_MOVIE_DATA_LOADING':
      return {
        ...state,                   // keep the existing state,
        loading: true,              // but change loading to true
      };
    case 'GET_MOVIE_DATA_RECEIVED':
      return {
        loading: false,             // set loading to false
        movies: action.data.movies, // update movies array with reponse data
      };
    case 'GET_MOVIE_DATA_ERROR':
      return state;
    default:
      return state;
    }
};

Update the App

Now, let’s wire our Redux storage into the app.

  • Open app.js file in src folder and add a few things:
import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import { apiMiddleware, reducer } from './redux';
  • Create Redux storage, apply apiMiddleware to it, and dispatch GET_MOVIE_DATA action right away:
// Create Redux store
const store = createStore(reducer, {}, applyMiddleware(apiMiddleware));

// Fetch movie data
store.dispatch({type: 'GET_MOVIE_DATA'});
  • And finally, wrap <Navigator> returned by render() method with <Provider store={store}></Provider> as follows:
export default class App extends Component {
  render() {
    return (
      <Provider store={store}>
        <Navigator
          // Default to movies route
          initialRoute={{ name: 'movies' }}
          // Use FloatFromBottom transition between screens
          configureScene={(route, routeStack) => Navigator.SceneConfigs.FloatFromBottom}
          // Pass a route mapper functions
          renderScene={RouteMapper}
        />
      </Provider>
    );
  }
}

That will make our Redux storage available to every component that needs it.

Update Movies Component

Finally, let’s update Movies component to use the data from Redux storage instead of JavaScript files.

  • Open Movies.js file in src folder and add a few more imports:
import { connect } from 'react-redux';
import {
  ActivityIndicator,
  RefreshControl,
  // ...others
} from 'react-native';
  • Connect Redux storage and refresh actions to Movies component using @connect decorator:
@connect(
  state => ({
    movies: state.movies,
    loading: state.loading,
  }),
  dispatch => ({
    refresh: () => dispatch({type: 'GET_MOVIE_DATA'}),
  }),
)
export default class Movies extends Component {
// ...
  • Update render() method to pull movies`, `loading`, and `refresh out of this.props and update <ScrollView> component:
  render() {
    const { movies, loading, refresh } = this.props;
    return (
      <View style={styles.container}>
        {movies
          ? <ScrollView
              contentContainerStyle={styles.scrollContent}
              // Hide all scroll indicators
              showsHorizontalScrollIndicator={false}
              showsVerticalScrollIndicator={false}
              refreshControl={
                <RefreshControl
                  refreshing={loading}
                  onRefresh={refresh}
                />
              }
            >
              {movies.map((movie, index) => <MoviePoster
                movie={movie}
                onOpen={this.openMovie}
                key={index}
              />)}
            </ScrollView>
          : <ActivityIndicator
              animating={loading}
              style={styles.loader}
              size="large"
            />
        }
        <MoviePopup
          movie={this.state.movie}
          isOpen={this.state.popupIsOpen}
          onClose={this.closeMovie}
          chosenDay={this.state.chosenDay}
          chosenTime={this.state.chosenTime}
          onChooseDay={this.chooseDay}
          onChooseTime={this.chooseTime}
          onBook={this.bookTicket}
        />
      </View>
    );
  }
  • Add flex: 1 to container style, and add loader style as follows:
const styles = StyleSheet.create({
  container: {
    flex: 1,                // take up all screen
    paddingTop: 20,         // start below status bar
  },
  loader: {
    flex: 1,
    alignItems: 'center',     // center horizontally
    justifyContent: 'center', // center vertically
  },
  // ...existing styles

All Set

And we’re all done. Let’s launch our app to make sure that everything is working as expected and movie data is being fetched from the API.

  1. Launch the API backend that we built in this tutorial API Backend with Node.js, Express, and MongoDB
  2. Launch the app by executing react-native run-ios in terminal.

And there it is. Try to click, hold, and pull down anywhere on the screen to see activity indicator shows up at the top of the screen. And once you release, it dispatches GET_MOVIE_DATA and updates the storage. So, if you changed your data in MongoDB, it would have been updated in the app.

Wrapping Up

Hopefully, you’ve enjoyed the tutorial and have learned a lot. Subscribe to get notified about new tutorials. And if you have any questions or ideas for new tutorials, just leave a comment below the post.

Spread the Word
  • Dominic Gichuhi

    Hello, when i execute the app, it keeps on loading forever… what could be the problem? I am running the app on android using genymotion as the emulator

    • Dominic Gichuhi

      I can be able to access the movie data via http://localhost:3000/v1/movies.json

    • When running the in Android emulator press ⌘ + M to access developer menu and select Debug JS Remotely. That should launch Chrome debugger. Then press ⌘ + SHIFT + J to open up the console. And see if there is any errors.

    • Hans Felipe

      SAME PROBLEM HERE :/ RN Chrome debugger tells me that there are no errors

      (i’m on windows, using genymotion and have all steps of this tutorial ok)

      • Arbeloa

        same thing here, I’m running the app on a device.

    • It turns out the problem is that if you are running the app on Android emulator, you can’t make network calls to localhost. You have to use 10.0.3.2 IP address if you’re using Genymotion. I’ve updated redux.js file.

      • Dominic Gichuhi

        Thanks. It is working now. Looking forward to more RN tutorials

  • Rui

    Thanks for the work, looking forward to more tutorials.
    I’d suggest you to make a real life example of an app with login and navigation with a more robust folder structure and also using redux. Oh, and what about offline first apps?

  • Vignesh Prasad

    Hi, thanks for all the tutorials. I’m facing a problem on this one though. The app is always loading. I followed the last two tutorials and they worked perfectly. The data is there when I open movies.json in the browser. Could you help me out please?