Flux Architecture For Mobile Apps With React Native And Redux

Flux is an application architecture conceived at Facebook to solve some of challenges they were experiencing with MV* pattern. It’s usually mentioned along with React and was made public around same time, however it’s not really specific to a library or framework. Unidirectional flow of the data is the core tenet of Flux. Any data changes as a result of user interaction are propagated as action through a central dispatcher to the store. store holds the application’s state, business logic and it notifies view of any changes in the state.

Core Concepts

  • (a) Actions: User actions and events are expressed as simple objects containing the name/ type of the action and relevant information.
  • (b) Stores: Application’s state is held in one or multiple store. Data in the store can’t be updated directly, there is only one way to receive updates for the data - the callbacks registered with dispatcher. Store emit a change event to notify the views that the data change has occurred. It’s responsibility of the view to determine whether it need to re-render to reflect the updated state.
  • (c) Dispatcher: It acts as a central hub which distributes actions to the stores. Stores register a callback with the dispatcher, that’s how dispatcher sends actions to the store.
  • (d) Views: Users interact with the application through the views, which dispatches action to make any changes to the state as a result. It also listens for the change notifications from the store and re-render as and when required.

Why Flux?

Definitely there is some learning curve involved and boilerplate code needed to make it all work together. However, there are many pros, which will pay it off very well:

  • Clean & Maintainable Code: Flux enforces discipline in the way data and UI changes needs to be handled. This brings some degree of consistency to the code base and make it easy to reason about.
  • Centralized State: store acts as a single source of truth. If you are displaying same information at multiple places or show derived information(i.e. unread messages), it would avoid duplication of data and overhead of keeping it in sync.
  • Manage Complexity: Action/ event driven design with unidirectional data-flow makes it easier to build much more complex UI.

Redux

As mentioned, Flux is a pattern or way of acrchitecting an application, it’s not a library or framework. You can write code to felicitate unidirectional data-flow and state change notification yourself, however it would be time consuming to maintain as your needs grow over time. Redux is one of the many implementations of Flux available. Redux has been optimized to reduce complexity and improve the development experience, so it does differ slightly from the pure Flux. Redux has picked up a lot of community mind-share and is widely used, so that is what I am using in the example in the following section.

Let’s Implement It

Let’s implement Flux in a React Native mobile app using Redux. Following are the redux and related packages we will be using

  • redux: as mentioned redux is a flux implementation with few differences, but it’s fairly close.
  • react-redux: redux works well with react, however it has no direct relation. react-redux provides react bindings for redux - glue for connecting react components to the redux store’s change notifications.
  • redux-thunk: many, if not most actions will require to communicate with the server, which is asynchronous by nature. redux-thunk provides the middleware that allows to write action creators which returns function instead of plain object representing the action. It will dispatch the action only when certain operation is finished(i.e. async call). Thunk is simply a function that wraps an expression to delay its evaluation.
  • react-native-router-flux: This isn’t really must, however it provides easy way to define all the transition in one location and call transition from anywhere in the code without passing the navigator object. It will help keep things decoupled.

Below, I have tried to breakdown the major parts in simple steps:

Configure the store
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducer from './reducer';

const createStoreWithMiddleware = applyMiddleware(thunk)(createStore);

function configureStore(initialState) {
  return createStoreWithMiddleware(reducer, initialState);
}

const store = configureStore(initState);  //initialState could be empty or it could be loaded with any data stored locally
Wrap root component with Provider and pass the store

Our react components need access to redux store for reading the data and receiving the change notifications. Provider makes the redux store available to the connect function(discussed below)

import React, { Component } from 'react';
import {
  AppRegistry,
  Text,
  View
} from 'react-native';
import { Provider, connect  } from 'react-redux';
import configureStore from './src/store';
import { bindActionCreators } from 'redux';
import Stories from './src/stories';
import Bookmarks from './src/bookmarks';
import { initState } from './src/constants';
import { Router,Route, Scene, TabBar, Switch, Reducer, Actions } from 'react-native-router-flux'; 

export default class ReactNativeFlux extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    const store = configureStore(initState);  //ToDo: any data stored locally could be passed in here

    return (
      <Provider store={store}>
        <Router HideNavBar={false}>
          <Scene key="stories" component={Stories} initial={true} />
          <Scene key="bookmarks" component={Bookmarks} />
        </Router>
      </Provider>
    );
  }
}

AppRegistry.registerComponent('ReactNativeFlux', () => ReactNativeFlux);
‘Connect’ Container Component to Store

Stateful components which provide data and behavior to the sub-components needs to be connected to redux store. Call to the connect function at the end is accomplishing this. You can see how the render is able to access the redux store as this.props.state.news.stories.

import React, { Component } from 'react';
import { View, AppRegistry, Text } from 'react-native';
import { Provider, connect  } from 'react-redux';
import configureStore from './store';
import style from './style';
import { bindActionCreators } from 'redux';
import * as actions from './actions';

class Stories extends Component {
  constructor(props) {
    super(props);
  }

  componentDidMount() {
    this.props.actions.fetchStories();
  }

  render() {

    return(
      <View style={style.container}>
        <Text>Story Count: {this.props.state.news.stories.length}</Text>
      </View>
    );
  }
}

//------ Container -----
function mapStateToProps(state) {
  return { state: state }
}

function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(actions, dispatch),
    dispatch
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(Stories);