React Native is introducing a new way to manage navigation state in your application: navigation components now talk to reducers instead of storing navigation state internally. Check out examples in the RN repository to get a feel for what it’s like to use these new components.
You can grab the code for this example here
June 30, 2016 Update
We’ve refactored reducers and actions into a separate package that you can reuse in your own project - you can grab it on Github
June 24, 2016 Update
React Native 0.28.0 introduced a bunch of breaking changes to experimental navigation - please refer to the repository for this example to get the latest version of this example
May 28, 2016 Update
The example has been customized for Android as well
Battle plan
After playing with basic examples and figuring out how to hook new navigation components to a Redux store, I wanted to move on and try it with a scenario that seems to be closer to what a real app would need navigation wise.
Let’s start from the end and see what the app is gonna look like
Notice how we have a few different navigation tiers - there’s a top-level card stack navigation that manages Tab views and a ‘New Item’ screen (top level gray screen with React logo).
Tab navigation lives on it’s own within one of the top level cards. We have 3 tabs in the app: Items, Notifications, Settings.
The next navigational tier is within the feed tab that shows a list of items in your application. Clicking on one of the items takes you to details view screen which is still embedded with the feed tab. An example of this on twitter is you clicking on a tweet to get more details on what’s going on with a tweet
First try
My initial attempt was to implement this in a single top level reducer. I did manage to get it to work with the scenario described above but it was not pretty
Not only it is super hard to reason about the behavior of this reducer, supporting and extending this looks like a nightmare. What I really need is to have separate reducers for all the different nav tiers and plug them in when needed within different parts of the application. Back to the drawing board.
Scoped sub navigators
What if we could tackle navigation in isolation starting from the top level, adding sub navigation within our component tree when needed. After all, since navigation control is expressed using reducers, nothing stops us from connecting certain components to these reducers as we go.
Step 1: Top level cards
The first step would be to get top level card navigation to work
Let’s define a reducer for this using RN’s StackReducer
We can now connect it to the navigation component using Redux connect
Step 2: Tabs
Our tab reducer is gonna be based on RN’s TabsReducer
Here’s how we attach this to our tab component
Quick recap
Notice how we now have 2 nav hierarchies in the app coexisting in peace. Corresponding nav reducers are compact and easy to reason about. Individual reducers are combined and exposed through application Redux store
Here’s what it looks like in the application store (don’t worry about the feed part, we are gonna see it in a second)
Step 3: List -> Details nav
Remember how we are supposed to be able to navigate from list for details view in the feed tab? Let’s implement this
Let’s start with defining a navigation reducer for the feed component. Once again, it is going to be a stack reducer:
We can now use a NavigationCardStack component
Adjusting reducer scope
An attentive reader might be able to point out the problem we are about to run into. Since our navigation reducers coexist within the app store, each of them will be trying to respond to an incoming event. With 2 stack reducers in the system (global navigation and list-details reducer), we are going to end up with ‘push’, ‘back’ and ‘BackAction’ actions clashing.
We could come up with special names for every new nav action but we will be losing ability to reuse pre-made reducers. A solution I would like to suggest it so use nav keys to scope both actions and reducers as follows
and a corresponding reducer now is
We can now adjust reducers and components to provide this scoping. You can find the updated version of project on Github.
Conclusion
New RN navigation approach is great for simplifying the way we reason about navigation and gets it closer to a Redux architecture which many of us are already using in React/RN apps. Setting up monolithic reducer for the app that combines multiple navigational modes has proven to be tedious and hard to maintain.
The proposed solution is to split navigation reduction into a series of scoped nav reducers where each individual reducer is responsible for a part of app navigation system while coordinating with other reducers using navigational key as a scope identifier.
This example is intentionally very verbose (e.g. scoping can be moved to a utility library and applied to both reducers and connector). The intention was to make this scoping very explicit to the reader. Below is an example of scoping reducers using a helper function
I would be curious to hear how you manage these navigation scenarios in your apps. You can play wit this example by grabbing the code from our github