React-Navigation
npm install react-navigation @rematch/react-navigation
For @rematch/[email protected] use @rematch/[email protected]
See an example.
Setting up React-Navigation with Redux is a multistep process. Hopefully this plugin simplifies the process.
- Create your
<Routes />
, and select the name of yourinitialRouteName
.
// Routes.js
export default StackNavigator(
{
Landing: {
screen: Screen.Landing,
},
Login: {
screen: Screen.Login,
},
App: {
screen: App,
},
},
{
initialRouteName: 'Landing',
}
)
- Pass
Routes
andinitialRouteName
intocreateReactNavigationPlugin
.
// index.js
import { init, dispatch } from '@rematch/core'
import createReactNavigationPlugin from '@rematch/react-navigation'
import * as ReactNavigation from 'react-navigation'
import Routes from './Routes'
// add react navigation with redux
const { Navigator, reactNavigationPlugin } = createReactNavigationPlugin({
Routes,
initialScreen: 'Landing'
})
const store = init({
plugins: [reactNavigationPlugin],
})
export default () => (
<Provider store={store}>
<Navigator />
</Provider>
)
- Use the plugins included navigation helpers to simplify dispatching Navigation actions.
// included in plugin
dispatch.nav.navigate = (action) => dispatch(NavigationActions.navigate(action))
dispatch.nav.reset = (action) => dispatch(NavigationActions.reset(action))
dispatch.nav.back = (action) => dispatch(NavigationActions.back(action))
dispatch.nav.setParams = (action) => dispatch(NavigationActions.setParams(action))
Just pass the NavigationAction options.
// somewhere in your app
import { dispatch } from '@rematch/core'
dispatch.nav.navigate({ routeName: 'Login' })
If necessary, import NavigationActions
.
import { dispatch } from '@rematch/core'
import { NavigationActions } from 'react-navigation'
const resetAction = dispatch.navigate.reset({
index: 1,
actions: [
NavigationActions.navigate({ routeName: 'Profile'}),
NavigationActions.navigate({ routeName: 'Settings'})
]
})
An example for setting up the Android back button handling with react-navigation.
import { dispatch, init } from '@rematch/core'
import createReactNavigation from '@rematch/react-navigation'
import React from 'react'
import { BackHandler } from 'react-native'
import { Provider } from 'react-redux'
import { Routes } from './Routes'
export class App extends React.Component {
componentDidMount() {
BackHandler.addEventListener('hardwareBackPress', this.handleBack)
}
componentWillUnmount() {
BackHandler.removeEventListener('hardwareBackPress', this.handleBack)
}
handleBack = () => {
if (store.getState().nav.index === 0) {
BackHandler.exitApp()
}
dispatch.nav.back()
return true
}
render() {
return (
<Provider store={store}>
<Navigator />
</Provider>
);
}
}
You may find it helpful to add some custom selectors to your nav
model. You can easily add selectors by creating a nav
model configuration object. This example will add a currentRouteName
selector:
// models/nav.js
export default {
selectors: {
currentRouteName(state) { return state.routes[state.index].routeName; },
},
}
Add the above model to your rematch configuration:
// models/index.js
export { default as nav } from './nav'
Ensure your new model in included in your init
:
import { select } from '@rematch/select'
import * as models from './models'
const store = init({
models,
plugins: [select, reactNavigationPlugin],
});
Of course, you will also need to install the Rematch Select plugin.
Now you will be able to call select.nav.currentRouteName(state)
from within your app. See the Rematch Select plugin documentation for more details on how to configure and use selectors.
If your store is not a standard Javascript Object { }
, then you may need to pass a sliceState
config to
createReactNavigationPlugin
. This function takes the state and returns the slice of the state that contains the
React Navigation object. It is used in the Navigator
to map the state to its props and it is used in the
middleware that listens for changes to the navigation state. The default is to use state.nav
which will work fine
in most cases.
If your store is an Immutable JS object
you may need to pass sliceState: state => state.get('nav')
as a config
to createReactNavigationPlugin
. Here is a minimal example:
// index.js
import { init, dispatch } from '@rematch/core'
import createReactNavigationPlugin from '@rematch/react-navigation'
import * as ReactNavigation from 'react-navigation'
import Routes from './Routes'
import { combineReducers } from 'redux-immutable';
import { Map } from 'immutable';
// add react navigation with redux
const { Navigator, reactNavigationPlugin } = createReactNavigationPlugin({
Routes,
initialScreen: 'Landing',
sliceState: state => state.get('nav') // Returns Immutable JS slice
})
const store = init({
initialState: fromJS({}),
plugins: [reactNavigationPlugin],
redux: {
initialState: Map(), // Initializes a blank Immutable JS Map
combineReducers: combineReducers, // Combines reducers into Immutable JS collection
},
})
export default () => (
<Provider store={store}>
<Navigator />
</Provider>
)
In the above example, your store is an Immutable JS object. However, the nav
slice of your store is a standard JS
object. I recommend this approach since the React Navigation navigators and Redux middleware expects the navigation state
to be a standard JS object. This is the most performant approach. The alternative is to store the navigation state as
an Immutable JS Map, which would require calls fromJS()
and toJS()
every time the navigator or the middleware
needs to access the Rematch state, which would cause a degradation in performance.