React-Navigation, complete Redux state management, tab-bar, and multiple navigators

Dan Parker
codeburst
Published in
7 min readFeb 5, 2017

EDIT: UPDATE 5–2–17 to the new React-Navigation API and newest current version of React-Native

  • At the time of this publishing, it has been 8 days since people smarter than me released yet another Navigator for React-Native…this could finally be the Navigator React-Native deserves.

GitHub repo here

I will write up a detailed example of the new react-navigation API using redux as the navigator’s state management.

Step One: Initialize a new project react-native init SampleNavigation

Step Two: Install dependencies. I will use react-native-vector-icons for the icons on the tab-bar.

npm install --save redux react-redux redux-logger react-navigation react-native-vector-icons && react-native link

Step Three: Write the code.

This is how I view the navigation structure. First, a tab-bar is it’s own navigator and each subsequent tab is also it’s own navigator. In this example I will use three tabs in a tab-bar, therefore four navigators, each with it’s own reducer and state.

I also divide my code into “features”, therefore my structure looks like this.

/app
/tabBar
/views
TabBarNavigation.js
navigationConfiguration.js
/tabOne
/views
TabOneNavigation.js
TabOneScreenOne.js
TabOneScreenTwo.js
navigationConfigutation.js
/tabTwo
/views
TabTwoNavigation.js
TabTwoScreenOne.js
TabTwoScreenTwo.js
navigationConfiguration.js
/tabThree
/views
TabThreeNavigation.js
TabThreeScreenOne.js
TabThreeScreenTwo.js
navigationConfiguration.js
store.js

Tab Bar Configuration

Start with the tab-bar as it is the entry point and top-most navigator. According to the docs, to construct a tab-bar you call a function with two arguments TabNavigator(RouteConfigs, TabNavigatorConfig) The RouteConfigs are the individual tabs, which in turn are Navigators. So the routes we provide to the tab-bar are the individual Tab-Navigators.

The routes are defined as key/value pairs like ScreenName: { screen: ScreenName } so the RouteConfigs is an array of possible routes, this case it’s an array of the possible tabs.

The TabNavigatorConfig are options provided by the API to customize your tab-bar…it’s just a JS object with key: value pairs, the most important one is tabBarOptions This is where you put your active/inactive colors.

My entire navigation configuration is all neatly contained in a separate JS file, ready for import and usage. It looks like this:

'use strict'
import { TabNavigator } from 'react-navigation'
// Tab-Navigators
import TabOneNavigation from '../tabOne/views/TabOneNavigation'
import TabTwoNavigation from '../tabTwo/views/TabTwoNavigation'
import TabThreeNavigation from '../tabThree/views/TabThreeNavigation'
const routeConfiguration = {
TabOneNavigation: { screen: TabOneNavigation },
TabTwoNavigation: { screen: TabTwoNavigation },
TabThreeNavigation: { screen: TabThreeNavigation },
}
const tabBarConfiguration = {
//...other configs
tabBarOptions:{
// tint color is passed to text and icons (if enabled) on the tab bar
activeTintColor: 'white',
inactiveTintColor: 'blue',
// background color is for the tab component
activeBackgroundColor: 'blue',
inactiveBackgroundColor: 'white',
}
}
export const TabBar = TabNavigator(routeConfiguration,tabBarConfiguration)

Tab Bar Configuration Complete

Before the tab-bar can become live and actually render something, we also need to set-up the individual Tab-Navigators so they can render a screen as well. These will be StackNavigators

Stack Navigator Configuration

According to the docs, to construct a StackNavigator you call a function with two arguments, much like the TabNavigator StackNavigator(RouteConfigs, StackNavigatorConfig) The StackNavigator has more available configs, see the docs for more information.

Simple configuration set up will be similar to the tab-bar configuration:

'use strict'import { StackNavigator } from 'react-navigation'// Screens
import TabOneScreenOne from './views/TabOneScreenOne'
import TabOneScreenTwo from './views/TabOneScreenTwo'
const routeConfiguration = {
TabOneScreenOne: { screen: TabOneScreenOne },
TabOneScreenTwo: { screen: TabOneScreenTwo },
}
// going to disable the header for now
const stackNavigatorConfiguration = {
headerMode: 'none',
initialRouteName: 'TabOneScreenOne'
}
export const NavigatorTabOne = StackNavigator(routeConfiguration,stackNavigatorConfiguration)

Do this for all three tabs, just changing the names…create simple screen components to render

'use strict'import React from 'react'
import { View, Text } from 'react-native'
export default class TabOneScreenOne extends React.Component { render(){
return(
<View style={{
flex:1,
backgroundColor:'red',
alignItems:'center',
justifyContent:'center'
}}>
<Text>{ 'Tab One Screen One' }</Text>
</View>
)
}
}

Stack Navigator Configuration Complete

Time to wire it all up!

Configure the redux-store.

There is for a handy helper method called getStateForAction that wires up the router and handles all the navigation logic.

Use it like this in your Redux store

'use strict'// Redux
import { applyMiddleware, combineReducers, createStore } from 'redux'
import logger from 'redux-logger'
// Navigation
import { NavigatorTabOne } from './tabOne/navigationConfiguration'
import { NavigatorTabTwo } from './tabTwo/navigationConfiguration'
import { NavigatorTabThree } from './tabThree/navigationConfiguration'
import { TabBar } from './tabBar/navigationConfiguration'
// Middleware
const middleware = () => {
return applyMiddleware(logger())
}
export default createStore(
combineReducers({
tabBar: (state,action) => TabBar.router.getStateForAction(action,state),
tabOne: (state,action) => NavigatorTabOne.router.getStateForAction(action,state),tabTwo: (state,action) => NavigatorTabTwo.router.getStateForAction(action,state),tabThree: (state,action) => NavigatorTabThree.router.getStateForAction(action,state),
}),
middleware(),
)

Index

'use strict'// React
import React from 'react'
import { AppRegistry } from 'react-native'
// Redux
import { Provider } from 'react-redux'
import store from './app/store'
// Navigation
import TabBarNavigation from './app/tabBar/views/TabBarNavigation'
class SampleNavigation extends React.Component {
render(){
return(
<Provider store={store}>
<TabBarNavigation />
</Provider>
)
}
}
AppRegistry.registerComponent('SampleNavigation', () => SampleNavigation)

Wiring on the Tab-Bar

Remember from the beginning of this article, to create a Tab-Bar we call a function with the arguments we created. To take control away from react-navigation and put things in our Redux State we need to supply the created tab-bar with navigation state and dispatch with the helper method supplied by react-navigation The created file for a Tab-Bar will look like this:

'use strict'// React
import React from 'react'
// Navigation
import { addNavigationHelpers } from 'react-navigation'
import { TabBar } from '../navigationConfiguration'
//Redux
import { connect } from 'react-redux'
const mapStateToProps = (state) => {
return {
navigationState: state.tabBar,
}
}
class TabBarNavigation extends React.Component {render(){
const { dispatch, navigationState } = this.props
return (
<TabBar
navigation={
addNavigationHelpers({
dispatch: dispatch,
state: navigationState,
})
}
/>
)
}
}
export default connect(mapStateToProps)(TabBarNavigation)

Wiring the Stack-Navigators for the individual Tabs

Pretty much the same as the Tab-Bar.

'use strict'// React
import React from 'react'
// Navigation
import { addNavigationHelpers } from 'react-navigation'
import { NavigatorTabOne } from '../navigationConfiguration'
// Redux
import { connect } from 'react-redux'
// Icon
import Icon from 'react-native-vector-icons/FontAwesome'
const mapStateToProps = (state) => {
return {
navigationState: state.tabOne
}
}
class TabOneNavigation extends React.Component {render(){
const { navigationState, dispatch } = this.props
return (
<NavigatorTabOne
navigation={
addNavigationHelpers({
dispatch: dispatch,
state: navigationState
})
}
/>
)
}
}
export default connect(mapStateToProps)(TabOneNavigation)

This should build, run, and navigate…but it’s ugly looking.

Let’s get rid of the ugly text and add some icons on iOS.

To change the text of the tabs and add icons, just put static navigationOptions on the individual tab-navigators. Remember in the configuration of the tabBar we supplied tintColors we now have access to those colors.

TabOneNavigation:

'use strict'// React
import React from 'react'
// Navigation
import { addNavigationHelpers } from 'react-navigation'
import { NavigatorTabOne } from '../navigationConfiguration'
// Redux
import { connect } from 'react-redux'
// Icon
import Icon from 'react-native-vector-icons/FontAwesome'
const mapStateToProps = (state) => {
return {
navigationState: state.tabOne
}
}
class TabOneNavigation extends React.Component {
static navigationOptions = {
tabBarLabel: 'Tab One',
tabBarIcon: ({ tintColor }) => <Icon size={ 20 } name={ 'cogs' } color={ tintColor }/>

}
render(){
const { navigationState, dispatch } = this.props
return (
<NavigatorTabOne
navigation={
addNavigationHelpers({
dispatch: dispatch,
state: navigationState
})
}
/>
)
}
}
export default connect(mapStateToProps)(TabOneNavigation)

Looks good…

Now let’s handle navigation within the tabs. I’ll put a button to navigate to a new route on all the tab’s first screens.

Tab One Screen One:

'use strict'
import React from 'react'
import { View, Text, TouchableOpacity } from 'react-native'
export default class TabOneScreenOne extends React.Component {
render(){
return(
<View style={{
flex:1,
backgroundColor:'red',
alignItems:'center',
justifyContent:'center'
}}>
<Text>{ 'Tab One Screen One' }</Text>
<TouchableOpacity
onPress={ () => this.props.navigation.navigate('TabOneScreenTwo') }
style={{
padding:20,
borderRadius:20,
backgroundColor:'yellow',
marginTop:20
}}>
<Text>{'Go to next screen this tab'}</Text>
</TouchableOpacity>
</View>
)
}
}

Tab One Screen Two:

'use strict'
import React from 'react'
import { View, Text, TouchableOpacity } from 'react-native'
export default class TabOneScreenTwo extends React.Component {
render(){
return(
<View style={{
flex:1,
backgroundColor:'orange',
alignItems:'center',
justifyContent:'center'
}}>
<Text>{ 'Tab One Screen Two' }</Text>
<TouchableOpacity
onPress={ () => this.props.navigation.goBack() }
style={{
padding:20,
borderRadius:20,
backgroundColor:'purple',
marginTop:20
}}>
<Text>{'Go back a screen'}</Text>
</TouchableOpacity>
</View>
)
}
}

Now all your navigation state is stored in your redux store.

Pretty easy to handle AndroidBack actions however you want to with this available information.

Want the back action to go back a screen on a certain tab? Simple…add a listener.

BackHandler.addEventListener('hardwareBackPress', this.backAction )backAction = () => {
// get the tabBar state.index to see what tab is focused
// get the individual tab's index to see if it's at 0 or if there is a screen to 'pop'
if (you want to pop a route) {
// get the navigation from the ref
const { navigation } = this.navigator.props
// pass the key of the focused route into the goBack action
navigation.goBack(navigation.state.routes[navigation.state.index].key)
return true
} else {
return false
}
}
<TabWhateverNavigator
ref={ (ref) => this.navigator = ref }
navigation={
addNavigationHelpers({
dispatch: dispatch,
state: navigationState
})
}
/>

Custom Actions/Reducer/Router/Whatever you call it

Want to jump tabs with a button on a screen?? I do…here’s a way.

Take the getStateForAction function and put it in the navigationConfiguration file so things look nicer. Have it intercept a custom action or just return the same function.

Like this works for my use case, in tabBar => navigationConfiguration

export const tabBarReducer = (state, action) => {
if (action.type === 'JUMP_TO_TAB') {
return { ...state, ...action.payload }
} else {
return TabBar.router.getStateForAction(action,state)
}
}

And in a Tab Three Screen a button…

<TouchableOpacity
onPress={
() => this.props.navigation.dispatch({ type:'JUMP_TO_TAB', payload:{index:0} })
}
style={{
padding:20,
borderRadius:20,
backgroundColor:'deeppink',
marginTop:20
}}>
<Text>{'jump to tab one'}</Text>
</TouchableOpacity>

And the new store

//...stuff and thingsimport { TabBar, tabBarReducer } from './tabBar/navigationConfiguration'// more thingsexport default createStore(
combineReducers({

//...other stuff and more things
tabBar: tabBarReducer,
}),
middleware(),
)

You can literally navigate to anywhere, from anywhere, with ease.

✉️ Subscribe to Codeburst’s once-weekly Email Blast, 🐦 Follow Codeburst on Twitter, and 🕸️ Learn Full Stack Web Development.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Published in codeburst

Bursts of code to power through your day. Web Development articles, tutorials, and news.

Written by Dan Parker

Fullstack Developer, super cute in 4th grade

Responses (38)

Write a response