Handling wix react native navigation with redux

There are many navigation options available for handling navigation in react native applications like react-native-router-flux, react-navigation,airbnb-native-navigation and wix-react-native-navigation out of which I like to use react-native-navigation because of its highly configurable navigation and screen options along with well maintained and updated codebase. React Native Navigation provides 100% native platform navigation on both iOS and Android for React Native apps and we have the option to create single screen as well as Tabbar based applications. The best part is the application screen transitions are smooth even in the dev build and any kind of issues are actively resolved by github codebase maintainers.

In this tutorial we are going to create a simple application and set up react-native navigation with redux store. First we need to make changes in android/ios files as explained in the official documentation here.

Next we install all redux and other required modules as follows:

$npm install --save react-redux react-native-linear-gradient 
react-native-vector-icons redux redux-immutable-state-invariant
redux-logger redux-thunk axios
$react-native link

Next we set up directory structure as follows:

Directory structure:-  
Entire JS specfic files are in src folder  
├── src  
│   ├── actions                            <- All screens level actions defined here  
│   │   └── homeActions.js   
│   ├── App.android.js                     <- Main app component containing Core Navigation Flow  
│   ├── constants                          <- All constants defined here   
│   │   ├── actionTypes.js                 <- Constants for actions defined here   
│   │   └── api.js                         <- Api base urls to be defined here    
│   ├── modules    
│   │   ├── appscreens                     <- Page level screens     
│   │   │   ├── FirstScreen.js    
│   │   │   ├── Home.js    
│   │   │   └── styles    
│   │   │       └── FirstScreenStyles.js   
│   │   └── global                         <-Global & Reusable Components    
│   │       ├── Drawer.js    
│   │       └── styles    
│   │           └── DrawerStyles.js    
│   ├── reducers                           <-Reducers for redux    
│   │   ├── appscreens    
│   │   │   └── homeReducer.js    
│   │   ├── initialState.js               <-Initial State data for redux     
│   │   └── rootReducer.js                <-Combine all screen level reducers here    
│   ├── screens.js                        <-All screens are defined here   
│   └── store                             <-Central Redux Store      
│       └── configureStore.js

We need to define initial state for the redux store inside the reducers folder as follows:

export default {
    projectData: {
        details: {
            "name":"some name"
        },
    }
};

The initialState is sometimes kept as empty jsons in some projects. I personally like to store offline data in initialState as it allows users to view application content event when they have no network connectivity.

Next we define action constants in actionTypes within constants folder:

export const RETRIEVE_COURSE_DETAILS = 'RETRIEVE_COURSE_DETAILS';

We can define all types of action names as constants in this file. Typically constants are defined to retrieve and send data from server and update state of store accordingly.

Now we need to define our page level reducers for our home screen in homeReducer.js in appscreens folder within reducers folder.

import * as types from '../../constants/actionTypes';
import initialState from '../initialState';
export default function (state = initialState.projectData, action) {
    switch (action.type) {
        case types.RETRIEVE_COURSE_DETAILS:
            return {
                ...state
            };
       default:
            return state;
    }
}

All kinds of ajax requests and logic/conditions to update state of store can be defined in this file.

Next we define rootReducer in reducers folder which is responsible for combining all page level reducers in a single entity.

import { combineReducers } from 'redux';
import homeReducer from './appscreens/homeReducer';
const rootReducer = combineReducers({
    homeReducer
});
export default rootReducer;

Now we will setup the central redux store which will be used by all components in the project in configureStore.js within store folder:

/* eslint-disable global-require */
/* eslint-disable no-undef */
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../reducers/rootReducer';
import {createLogger} from 'redux-logger';
let middleware = [thunk];
if (__DEV__) {
    // const reduxImmutableStateInvariant = require('redux-immutable-state-invariant')();
    const logger = createLogger({ collapsed: true });
    middleware = [...middleware,logger];
} else {
    middleware = [...middleware];
}
export default function configureStore(initialState) {
    return createStore(
        rootReducer,
        initialState,
        applyMiddleware(...middleware)
    );
}

By this step our redux setup is complete and we can move forward towards creating application screens.First let us create a simple HomePage to be displayed as first screen in Home.js within modules->appscreens folder:

import React, { PropTypes, Component } from 'react';
import {
    Text,
    View
} from 'react-native';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as homeActions from '../../actions/homeActions';
class Home extends Component {
    render() {
        return(
            <View>
                <Text>Home Page Content added here</Text>
            </View>
        )
    }
}
function mapStateToProps(state, ownProps) {
    return {
        courseData:state
    };
}
function mapDispatchToProps(dispatch) {
    return {
        actions: bindActionCreators(homeActions, dispatch)
    };
}
export default connect(mapStateToProps, mapDispatchToProps)(Home);

Now we need to register our page in navigation screens to be used by react-native-navigation.

/* eslint-disable import/prefer-default-export */
import { Navigation } from 'react-native-navigation';
import Home from './modules/appscreens/Home';
export function registerScreens(store, Provider) {
        Navigation.registerComponent('app.Home', () => Home, store, Provider);
}

Now we need to bring this all components together in App.js:

import React from 'react'; // eslint-disable-line
import { Provider } from 'react-redux';
import { Navigation } from 'react-native-navigation';
import { registerScreens } from './screens';
import configureStore from './store/configureStore';
const store = configureStore();
registerScreens(store, Provider);
const navigatorStyle = {
    statusBarColor: 'black',
    statusBarTextColorScheme: 'light',
    navigationBarColor: 'black',
    navBarBackgroundColor: '#0a0a0a',
    navBarTextColor: 'white',
    navBarButtonColor: 'white',
    tabBarButtonColor: 'red',
    tabBarSelectedButtonColor: 'red',
    tabBarBackgroundColor: 'white',
    topBarElevationShadowEnabled: false,
    navBarHideOnScroll: true,
    tabBarHidden: false,
    drawUnderTabBar: true
};
// If you need to start single screen app
Navigation.startSingleScreenApp({
    screen: {
        screen: 'app.Home',
        title: 'Sample App',
        navigatorStyle:navigatorStyle,
        leftButtons: [
            {
                id: 'sideMenu'
            }
        ]
    },
});

Last but not the least edit index.js file as follows:

import App from './src/App';

Now you can see a simple application as follows:

App screen

Next we will add a drawer to the app in drawer in modules->global folder and update app.js as follows:

import React, { PropTypes, Component } from 'react';
import {
    Text,
    ToastAndroid,
    TouchableOpacity,
    View
} from 'react-native';
import Icon from 'react-native-vector-icons/Ionicons';
import LinearGradient from 'react-native-linear-gradient';
import styles from './styles/DrawerStyles';
export default class Drawer extends Component {
    showAlert(event){
        this.toggleDrawer();
        ToastAndroid.show('Coming Soon!', ToastAndroid.SHORT);
    }
    toggleDrawer() {
        this.props.navigator.toggleDrawer({
            to: 'closed',
            side: 'left',
            animated: true
        });
    }
    goToHome(event) {
        this.toggleDrawer();
        this.props.navigator.popToRoot({
            screen: 'app.Home'
        });
    }
    openFirstPage(event){
        this.toggleDrawer();
        this.props.navigator.showModal({
            screen: 'app.FirstScreen',
            title: 'FirstScreen'
        });
    }
    render() {
        const iconHome = (<Icon name="ios-home" size={26} color="#9F9F9F" style={[styles.drawerListIcon, { paddingLeft: 2 }]} />);
        const iconMovies = (<Icon name="md-film" size={26} color="#9F9F9F" style={[styles.drawerListIcon, { paddingLeft: 3 }]} />);
        const iconTV = (<Icon name="ios-desktop" size={26} color="#9F9F9F" style={styles.drawerListIcon} />);

        return(
            <LinearGradient colors={['rgba(0, 0, 0, 0.7)', 'rgba(0,0,0, 0.9)', 'rgba(0,0,0, 1)']} style={styles.linearGradient}>
                <View style={styles.container}>
                    <View style={styles.drawerList}>
                        <TouchableOpacity onPress={(event) => this.goToHome(event)}>
                            <View style={styles.drawerListItem}>
                                {iconHome}
                                <Text style={styles.drawerListItemText}>
                                    Home
                                </Text>
                            </View>
                        </TouchableOpacity>
                        <TouchableOpacity onPress={(event) => this.openFirstPage(event)}>
                            <View style={styles.drawerListItem}>
                                {iconTV}
                                <Text style={styles.drawerListItemText}>
                                    Screen1
                                </Text>
                            </View>
                        </TouchableOpacity>
                        <View style={styles.drawerListItem}>
                            {iconTV}
                            <Text style={styles.drawerListItemText} onPress={(event) => this.showAlert(event)}>
                                    Screen2
                            </Text>
                        </View>
                    </View>
                </View>
            </LinearGradient>
        )
    }
}

App.js:

Navigation.startSingleScreenApp({
    screen: {
        ...
    },
    drawer: {
        left: {
            screen: 'app.Drawer'
        }
    }
});

Screens.js

/* eslint-disable import/prefer-default-export */
import { Navigation } from 'react-native-navigation';
import Drawer from './modules/global/Drawer';
import Home from './modules/appscreens/Home';
import FirstScreen from './modules/appscreens/FirstScreen';
export function registerScreens(store, Provider) {
        Navigation.registerComponent('app.Drawer', () => Drawer);
        Navigation.registerComponent('app.Home', () => Home, store, Provider);
        Navigation.registerComponent('app.FirstScreen', () => FirstScreen, store, Provider);
}

I have added a simple FirstScreen page similar to home to illustrate use of navigation options in drawer.

You can see the drawer as follows:

This concludes today’s tutorial for setting up react-native-navigation. You can find the entire source code which can be used directly to create applications in future here: Github

Bonus Tips:

You can define suitable actions to call within screens in actions folder by creating actions file for each screen. I have not covered making ajax requests in this article to keep the tutorial as simple as possible.

And kindly also star June Domingo’s opensource movie app on Github since the directory structure and coding style is mainly based on that application.

Connect Deeper:

In the next part we are going to cover populating home screen with content by making ajax requests to server. In order to get notified you can follow our facebook page: Technoetics or subscribe to our mailing list below:

Technoetics list

No Comments Yet

Add a comment