This article is about integrating  react native with redux in a way that I find easy to set up and work with. It is based on what I have learnt from scaling up React applications. Click here to view the source code.

Redux is a predictable state container for JavaScript apps. It is an awesome tool for handling your application’s state. If you google react native and redux set up you will find that a lot of people have different was setting up their projects. I am more comfortable with the fractal folder structure approach .

Okay, let’s start by creating our a simple react-native application. I am using a mac but you can still follow through on windows. If you haven’t set up your development environment, please checkout the getting started guide on react native’s official website.

1. Create a new application.

Open your terminal and use the  React Native command line interface to generate a new React Native project called “ReactNativeRedux”:

react-native init ReactNativeRedux

Run your application from within the “ReactNativeRedux” folder. Use “react-native run-ios” for iOS and “react-native run-android” for android.

cd ReactNativeRedux

react-native run-ios

You should get something like this:

2. Install Dependencies

Now we need to install the following npm packages.

  • redux – to manage the state of the app.
  • react-redux – react bindings
  • react-native-router-flux – for navigating between screens.
  • redux-thunk – middleware allows you to write action creators that return a function instead of an action.
  • immutability-helper – Mutate a copy of data without changing the original source.
  • redux-logger – logs redux actions + state in your browser.

Let us first install three packages by running the following commands in our terminal.

npm install redux react-redux redux-thunk react-native-router-flux immutability-helper –save

After that we can go ahead and install redux-logger as a dev dependency buy running the following command..

npm install redux-logger –save-dev

3. Project Structure

I am going to use a Fractal folder structure you can read more about it from David Zukowski’s github wiki. Our project will look something like this:

...
├── src                      # Application source code
│   ├── AppContainer         # Main Application container
│   │   ├── index.js         
│   ├── main.js              # Application bootstrap and rendering
│   ├── components           # Global Reusable Components
│   ├── routes               # Main route definitions and async split points
│   │   ├── Home             # Fractal route
│   │   │   ├── container    # Connect components to actions and store
│   │   │   ├── components   # Presentational React Components
│   │   │   └── modules      # Collections of reducers/constants/actions
│   ├── store                # Redux-specific pieces
│   │   ├── createStore.js   # Create and instrument redux store
│   │   └── reducers.js      # Reducer registry
...

Create the src directory in the root of your project folders you should end up with something like this:

 

In the root folder delete “App.js” which was auto generated for us by react-native-cli.

4. Setting Up the project with Redux

Now that we have our folder structure in place, we can go ahead and start coding. Let’s start by opening index.js in project’s root and replace its content with the following:

./index.js

import { AppRegistry } from 'react-native';
import App from './src/main';

AppRegistry.registerComponent('ReactNativeRedux', () => App);

AppRegistry is the JS entry point to running all React Native apps. Our app will be bootstrapped and rendered from “./src/main.js”.

./src/main.js

import React from "react";

import createStore from "./store/createStore";
import AppContainer from "./AppContainer";


export default class Root extends React.Component{
  renderApp(){
    const store = createStore();
    return (
      <AppContainer store={store} />
    );
  }
  render(){
    return this.renderApp();
  }
}

We still need to create the store and the AppContainer component. The store will be passed to the AppContainer as props for use with react-redux Provider.

./src/store/createStore.js

import { createStore, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import makeRootReducer from "./reducers";
import { createLogger } from "redux-logger";


const log =  createLogger({ diff: true, collapsed: true });

// a function which can create our store and auto-persist the data
export default (initialState = {}) => {
  // ======================================================
  // Middleware Configuration
  // ======================================================
  const middleware = [thunk];
  if(global.__DEV__){
    middleware.push(log);
  }

  // ======================================================
  // Store Instantiation
  // ======================================================
  const store = createStore(
    makeRootReducer(),
    initialState,
    compose(
      applyMiddleware(...middleware)
    )
  );
  return store;
};

In this file we write a function that creates our store. Our initialState object is empty but you can use it to set your applications initial state. Under middleware configuration we create our middleware array. As stated earlier, middleware allows you to write action creators that return a function instead of an action. We are then conditionally pushing the logger to the middleware because it  is only used during development.

./src/AppContainer/index.js

//LIBRARIES
import React, { Component } from "react";
import { StyleSheet } from "react-native";
import { Router, Scene, Drawer } from "react-native-router-flux";
import { Provider } from "react-redux";
import { View } from "react-native";

//COMPONENTS
import HomeContainer from "../routes/Home/container/HomeContainer";
export default class AppContainer extends Component {
  render(){
    return (
      <Provider store={this.props.store}>
        <Router>
          <Scene key="root">
            <Scene key="home" component={HomeContainer} hideNavBar/>
          </Scene>
        </Router>
      </Provider>
    );
  }
}
const styles = StyleSheet.create({
  container: {
    flex: 1
  }
});

AppContainer is just a react component. The most important thing here is we passing the store to the Provider which is a lightweight dependency injection mechanism. Your components can now make use / access the redux store easily. In this file we are also importing the “Home” component from “HomeContainer” which we are going to create soon.

react-native-router-flux is a routing package that allows you to:

  • Define scene transitions in one central location
  • Without having to pass navigator objects around, and allow you to
  • Call transitions anywhere in your code with a simple syntax (e.g. Actions.login({username, password}) or Actions.profile({profile}) or even Actions.profile(123) – all params will be part of this.props for given Scene component).

The syntax for creating routes can  be seen above. The scenes aka routes act as separate views.

./src/routes/Home/components/Home.js

Previously we created a few folders in the routes directory. Let’s see how we can make use of them starting with the react “Home” component. The components folder is where all our react components specific to the Home route will reside. Each route will have a main container component where we can import the rest of the components specific to this route.

//LIBRARIES
import React from "react";
import { Text, View, Button } from "react-native";

//STYLES
import styles from "./HomeStyles";

class Home extends React.Component{
  render(){
    return (
      <View style={styles.container}>
        <Text>Hello {this.props.name}</Text>
        <Button
          onPress={()=>this.props.changeName("Eman")}
          title="Change Name"
          color="#841584"
          accessibilityLabel="Learn more about this purple button"
        />
      </View>
    );
  }
}
export default Home;

In this route we have a prop called name <Text>Hello {this.props.name}</Text> which we are going to change when a button is clicked.  We are going to wire up everything soon.

./src/routes/components/HomeStyles.js

Styling for this component will be written in “HomeStyles.js”, so make sure you create this file in the same folder as the “Home.js” component.

import { StyleSheet, Dimensions } from "react-native";
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent:"center",
    alignItems:"center"
  }
});
export default styles;

/src/routes/module/actionConstants.js

In this file we simply create an object with our action constants which we then import in “./src/routes/module/index.js”

export default {
  CHANGE_NAME:"CHANGE_NAME"
};

./src/routes/module/index.js

This is were we keep our collection of reducers, constants and actions. First we import update to help us mutate a copy of the state without affecting the original source. We then import the constants object from “actionConstants.js”.

The function changeName will be called when a button is clicked. The function dispatches an action called CHANGE_NAME with a payload to mutate the sate. The initialState in this case is a name “John”. We will use redux to change the name to something else when the button is clicked.

Next we need to create an action handler to mutate the name state. The action handle uses update imported from immutability-helper  which helps us change the name’s state without changing its original source.

//LIBRARIES
import update from "immutability-helper";
import constants from "./actionConstants";
const {
  CHANGE_NAME
} = constants;

//ACTION CREATORS
//change name
export function changeName(payload){
  return {
    type:CHANGE_NAME,
    payload
  };
}
//ACTION HANDLERS
//handle name change
function handleChangeName(state, action){
  return update(state, {
    name:{
      $set:action.payload
    }
  });
}

const ACTION_HANDLERS = {
  CHANGE_NAME:handleChangeName
};

const initialState = {
  name:"John"
};

export function HomeReducer (state = initialState, action){
  const handler = ACTION_HANDLERS[action.type];
  return handler ? handler(state, action) : state;
}

./src/routes/store/reducers.js

In file we combine all the reducers from our app into one single “root reducer” which is the passed to the store. We split reducers to reduce complexity as the app grows. I this file we only have one reducer imported from the Home route module folder.

import { combineReducers } from "redux";
import { HomeReducer as home } from "../routes/Home/module";
export const makeRootReducer = () => {
  return combineReducers({
    home
  });
};

export default makeRootReducer;

./src/routes/container/HomeContainer.js

Previously in “./src/AppContainer/index.js” we “provided” our redux store to our application, now we can go ahead and connect our components to it. There is no direct way for react to interact with the store. Data can only be retrieved by obtaining current state or dispatching an action to alter the state.  connect does the “connecting” for us as seen below.

//LIBRARIES
import { connect } from "react-redux";
//COMPONENTS
import Home from "../components/Home";

import {
  changeName
} from "../module";

const mapStateToProps = (state) => ({
  name:state.home.name
});

const mapActionCreators = {
  changeName
};
export default connect(mapStateToProps, mapActionCreators)(Home);

With all that set up lets run our app and see react-native + redux in action.

cd ReactNativeRedux

react-native run-ios

 

 

Clicking on the button should change the name. To view the original source code please click here.

Categories: Text

Leave a Reply

Your email address will not be published. Required fields are marked *