Navigation in React Native: Examining and Understanding the Options

Nader Dabit

@dabit3

Twitter

Github

Mobile routing and navigation is hard.

  1. Different paradigm than web routing
  2. Navigation flows are hard to reproduce
  3. Navigation UX is different each platform

What we will cover:

  1. NavigatorIOS
  2. Navigator
  3. Navigator Experimental

Ships with React Native

From the Community

  1. React Native Navigation
  2. React Native Router Flux
  3. React Native Simple Router
  4. ExNavigation from Exponent

Current State of Native Implementations

NavigatorIOS

  •  Leverages the iOSUINavigationController class with very smooth native animations

PROS

  • Easy to get started
  • Intuitive api
  • Great for beginners or small projects

Current State of Native Implementations

NavigatorIOS

  • Not maintained by Facebook
  • iOS Only
  • Limited API

CONS

NavigatorIOS

Creating an instance of NavigatorIOS

import {
...
NavigatorIOS
} from 'react-native'

1. Import NavigatorIOS from React Native

2. Create or import an initial route

import Home from '../pathtohome'

3. Return the Navigator in render

render () {
  return (
    <NavigatorIOS
      initialRoute={{
        component: Home,
        title: 'Home'
      }} />
  )
}

NavigatorIOS

Pushing and popping to the route stack

this.props.navigator.push({
  component: About,
  title: 'About'
})
this.props.navigator.pop()

2. Popping to a previous route (go back)

1. Pushing to a new route

NavigatorIOS

Pushing and popping to the route stack

this.props.navigator.push({
  component: About,
  title: 'About',
  passProps: {
    name: 'React Camp',
    city: 'New York'
  }
})

Passing properties

Current State of Native Implementations

Navigator

PROS

  • Cross platform
  • Extensive API
  • Well documented
  • Many examples and resources online

Current State of Native Implementations

Navigator

CONS

  • Animations less refined than NavigatorIOS
  • Difficult to manage navigation state
  • Will soon be replaced by Navigator Experimental

Navigator

Creating an instance of Navigator

import {
...
Navigator
} from 'react-native'

1. Import Navigator from React Native

2. Create or import an initial route

import Home from '../pathtohome'

3. Create renderScene method

renderScene (route, navigator) {
  return <route.component
           navigator={navigator}
           {...route.passProps} />
}

Navigator

Return instance of Navigator:

 <Navigator
   renderScene={this.renderScene.bind(this)}
   initialRoute={{
     component: Home
   }} />

Pushing and popping to the route stack:

this.props.navigator.push({
  component: About
})
this.props.navigator.pop()

2. Popping to a previous route (go back)

1. Pushing to a new route

Navigator

 <Navigator
   configureScene={this.configureScene.bind(this)}
   renderScene={this.renderScene.bind(this)}
   initialRoute={{
     component: Home
   }} />
configureScene (route) {
  if (route.type === 'modal') {
    return Navigator.SceneConfigs.FloatFromBottom
  }
  return Navigator.SceneConfigs.FloatFromRight
}

1. Create configureScene method (available options)

2. Attach it to the navigator

this.props.navigator.push({
  ...
  type: 'modal'
})

3. Pass type of modal when we want a modal

Current State of Native Implementations

Navigator Experimental

  • Still in Development
  • Single-directional data flow, using reducers to manipulate top-level state object
  • Modular
  • You can implement custom navigation views in JS or Native or you can use pre-built scene and overlay components that are meant to look consistent with platform conventions.

Navigator Experimental

Creating an instance of Navigator Experimental

Navigator Experimental

Creating an instance of Navigator Experimental

import React from 'react'
import { AppRegistry } from 'react-native'

import configureStore from './app/store/configureStore'
const store = configureStore()

import NavigationRootContainer from './app/containers/navRootContainer'
import { Provider } from 'react-redux'

const App = () => (
  <Provider store={store}>
    <NavigationRootContainer />
  </Provider>
)
AppRegistry.registerComponent('RNComprehensiveNavigation', () => App)

Entrypoint (index.ios.js / index.android.js)

Navigator Experimental

Creating an instance of Navigator Experimental

import { POP_ROUTE, PUSH_ROUTE } from '../constants/ActionTypes'

export function push (route) {
  return {
    type: PUSH_ROUTE,
    route
  }
}

export function pop () {
  return {
    type: POP_ROUTE
  }
}

actions/actions.js

Navigator Experimental

Creating an instance of Navigator Experimental

import { PUSH_ROUTE, POP_ROUTE } from '../constants/ActionTypes'
const initialState = {
  index: 0,
  key: 'root',
  routes: [{ key: 'home', title: 'Home' }]
}
export default (state = initialState, action) => {
  switch (action.type) {
    case PUSH_ROUTE:
      return {
        ...state,
        routes: [
          ...state.routes,
          action.route
        ],
        index: state.index + 1
      }
    case POP_ROUTE:
      return state.index > 0 ? {
        ...state,
        routes: state.routes.slice(0, state.routes.length - 1),
        index: state.index - 1
      } : state
    default:
      return state
  }
}

reducers/navReducer.js

Navigator Experimental

Creating an instance of Navigator Experimental

import { connect } from 'react-redux'
import { push, pop } from '../actions/navActions'
import NavigationRoot from '../components/NavRoot'

function mapStateToProps (state) {
  return {
    navigation: state.navReducer
   }
}

function mapDispatchToProps (dispatch) {
  return {
    pushRoute: (route) => dispatch(push(route)),
    popRoute: () => dispatch(pop())
  }
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(NavigationRoot)

navRootContainer.js

Navigator Experimental

Creating an instance of Navigator Experimental

import React, { Component } from 'react'
import Home from './Home'
import About from './About'

import {
  NavigationExperimental
} from 'react-native'

const {
  CardStack: NavigationCardStack
} = NavigationExperimental

containers/navigationRoot.js

Navigator Experimental

_renderScene (props) {
  const { route } = props.scene
    if (route.key === 'home') {
      return <Home _handleNavigate={this._onNavigate.bind(this)} />
  }
  if (route.key === 'about') {
    return <About _goBack={this.props.popRoute.bind(this)} />
  }
}

containers/navRoot.js - renderScene

{
  key: 'home',
  title: 'Home'
}

props.scene.route

routes: [{
  key: 'home',
  title: 'Home'
}]

reducer initial route array

Navigator Experimental

_onNavigate (action) {
    switch (action.type) {
      case 'push':
        this.props.pushRoute(action.route)
        return true
      case 'pop':
        this.props.popRoute()
        return true
      default:
        return false
    }
}

containers/navRoot.js - renderScene

Navigator Experimental

render () {
  return (
    <NavigationCardStack
      navigationState={this.props.navigation}
      onNavigate={this._onNavigate.bind(this)}
      renderScene={this._renderScene.bind(this)} />
    )
}

rendering the NavigationCardStack component

React Native Navigation

Community / Open Source implementations

  1. Open sourced and by Wix
  2. App-wide support for 100% native navigation with an easy cross-platform interface.
  3. Intuitive and easy to reason about 
  4. Easy to get started without too much work, much easier than navigator or navigator experimental in my opinion
  5. Very smooth animations because they are native

React Native Navigation

Community / Open Source implementations

import { Navigation } from 'react-native-navigation'

import { registerScreens } from './screens'
registerScreens()

Navigation.startTabBasedApp({
  tabs: [
    {
      label: 'One',
      screen: 'FirstTabScreen',
      icon: require('./img/one.png'),
      selectedIcon: require('./img/one_selected.png'),
      title: 'Scrseen One'
    },
    {
      label: 'Two',
      screen: 'SecondTabScreen',
      icon: require('./img/two.png'),
      selectedIcon: require('./img/two_selected.png'),
      title: 'Screen Two'
    }
  ]
})
npm i react-native-navigation --save

React Native Navigation

import { Navigation } from 'react-native-navigation';

import FirstTabScreen from './FirstTabScreen';
import SecondTabScreen from './SecondTabScreen';
import PushedScreen from './PushedScreen';

export function registerScreens() {
  Navigation.registerComponent('FirstTabScreen', () => FirstTabScreen);
  Navigation.registerComponent('SecondTabScreen', () => SecondTabScreen);
  Navigation.registerComponent('PushedScreen', () => PushedScreen);
}

screens.js

navigate () {
  this.props.navigator.push({
    screen: 'PushedScreen'
  })
}

push route

this.props.navigator.pop()

pop route

Community / Open Source implementations

React Native Simple Router

  1. Cross platform
  2. Open sourced by react-native-simple-router-community
  3. Easy to install
  4. Very customizable
  5. Also great for beginners
  6. Documentation is concise and easy to understand as well as the api

Community / Open Source implementations

React Native Simple Router

npm install react-native-simple-router --save
import Router from 'react-native-simple-router'
import Home from './Home'

const firstRoute = {
  name: 'Home!',
  component: Home
}

class RNComprehensiveNavigation extends Component {
  render() {
    return (
      <Router
        firstRoute={firstRoute}
        headerStyle={styles.header} />
    )
  }
}

Community / Open Source implementations

React Native Simple Router

this.props.toRoute({
  name: 'About',
  component: About,
  passProps: {
    title: 'Hello From React Camp'
  }
})

this.props.toBack()

Basic navigator methods

Community / Open Source implementations

React Native Router Flux

  1. Open sourced by Pavel Aksonov
  2. Built on top of the new Navigator Experimental api and will be updated as the api changes and matures
  3. Allows you to Define scene transitions in one central location
  4. Similar api to react-router if coming from web
  5. Call navigation actions from anywhere in the app without having to keep up with the navigator prop
  6. Highly customizable
  7. Possible to define navigation state using reducers and redux

Community / Open Source implementations

React Native Router Flux

npm i react-native-router-flux --save
import React from 'react'
import { AppRegistry } from 'react-native'
import {Scene, Router} from 'react-native-router-flux'
import Home from './Home'
import About from './About'
import More from './More'

class RNComprehensiveNavigation extends React.Component {
  render() {
    return <Router>
      <Scene key='root'>
        <Scene key='home' component={Home} title='Home' />
        <Scene key='about' component={About} title='About'/>
        <Scene key='more' component={More} title='More'/>
      </Scene>
    </Router>
  }
}

React Native Router Flux

import { Actions } from 'react-native-router-flux'

Actions.about()
Actions.home()
Actions.more()
Actions.pop()

// Pass Props
Actions.more({
  firstName: 'Nader',
  lastName: 'Dabit',
  conference: 'React Camp'
})

Scenes

<Scene key='home' component={Home} title='Home' />
<Scene key='about' component={About} title='About'/>
<Scene key='more' component={More} title='More'/>

Methods

Pass all props with passProps boolean

<Scene key='home' component={Home} title='Home' passProps />

Community / Open Source implementations

ExNavigation by Exponent

  1. Built and maintained by Exponent + contributors
  2. Uses Navigation Experimental
  3. Includes cross platform Tab Navigation out of the box
  4. Includes easy to implement DrawerNavigation & Tabs out of the box
  5. API Somewhat similar to Navigator (push, pop, replace, etc...) 

ExNavigation

npm i @exponent/ex-navigation babel-preset-react-native-stage-0 --save

Router (Create app Routes)

import {
  createRouter
} from '@exponent/ex-navigation';

import Home from './Home'
import About from './About'

const Router = createRouter(() => ({
  home: () => Home,
  about: () => About,
}));

export default Router
import Router from './Router'
import {
  NavigationProvider,
  StackNavigation,
} from '@exponent/ex-navigation';

const App = () => (
  <NavigationProvider router={Router}>
    <StackNavigation
      defaultRouteConfig={{
        navigationBar: {
          backgroundColor: '#000',
          tintColor: '#fff',
        }
      }}
      initialRoute={Router.getRoute('home')} />
  </NavigationProvider>
)
  

ExNavigation

StackNavigation Component

import Router from './Router'

const people = ['amy', 'matt', 'leslie']

export default class Home extends Component {
  static route = {
    navigationBar: {
      title: 'Home',
    }
  }
  _goToAbout = () => {
    this.props.navigator.push(Router.getRoute('about', {people}));
  }
  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.welcome}>
          Welcome to Home
        </Text>
        <TouchableHighlight onPress={this._goToAbout}>
          <Text>Go to About</Text>
        </TouchableHighlight>
      </View>
    );
  }
}

Home Component

ExNavigation

 class SignOutButton extends Component {
   render() {
      return (
        <TouchableOpacity onPress={() => console.log('hello')}>
          <Text style={{marginRight: 15, marginTop: 12, color: 'white'}}>Sign out</Text>
        </TouchableOpacity>
      );
   }
}
export default class About extends Component {
  static route = {
    navigationBar: {
      title: 'About',
      renderRight: () => <SignOutButton />
    }
  }
  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.welcome}>
          Welcome to About
        </Text>
        {
          this.props.people.map((p, i) => {
            return <Text key={i}>{p}</Text>
          })
        }
      </View>
    );
  }
}

About Component

ExNavigation

ExNavigation

Community / Open Source implementations

React Router Native

  1. Built by Jake Murzy
  2. Uses Navigation Experimental
  3. Similar API to React Router
  4. URL Driven (easier to understand for people coming from the web)
  5. Cross Platform
  6. Enables deep linking

React Router Native

const Master = (props) => (
  <View>
    {props.children}
  </View>
);

const routes = (
  <Router history={nativeHistory} addressBar>
    <StackRoute path="master" component={Master}>
      <Route path="/" component={Home} overlayComponent={HomeHeader} />
      <Route path="/detail/:themeColor"
        component={Detail}
        overlayComponent={DetailHeader} />
    </StackRoute>
  </Router>
);

AppRegistry.registerComponent('YourApp', () => () => routes);

React Native Navigation - Takeaways

  1. NavigatorIOS is iOS Only
  2. Navigator is being deprecated - would probably not use
  3. Navigation Experimental is the new standard router
  4. Navigation Experimental uses Redux Style reducers to handle navigation state
  5. If you are used to React Router, try React Router Native
  6. If you like a more imperative approach, use ExNavigation

Comprehensive Navigation in React Native

  1. NavigatorIOS
  2. Navigator
  3. Navigator Experimental
  4. React Native Navigation
  5. React Native Simple Router
  6. React Native Router Flux
  7. ExNavigation
  8. React Router Native

Comprehensive Navigation in

React Native

Nader Dabit

React Native Radio

React Native in Action - Manning Publications

Navigation in React Native - Examining and Understanding the Options

By Nader Dabit

Navigation in React Native - Examining and Understanding the Options

React Remote Conf 2016

  • 5,038