React-Navigation, complete Redux state management, tab-bar, and multiple navigators
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.
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.