Simple routing in React Native with React Native Easy Router
Things have improved in the React Native routing ecosystem— there are no longer several “official” navigators competing with community efforts — but the currently popular libraries still seem unwieldy, overly prescriptive and not performant enough.
I’ve used react-navigation (as a hopefully helpful early adopter), and react-native-router-flux extensively, but have been on the lookout for an alternative that would:
- Be simple —I just want to specify routes, and have functions to change the stack of screens displayed
- Not prescribe UI —yes, I want pretty tabs and drawers, but I want those determined by my app, not the router
- Have elegant screen transitions
Sergey Shpadyrev’s react-native-easy-router fits the bill. I’ve made a few contributions to the library myself.
A simple but illustrative example app
Here is a demo app, showing the library in action. Clone and run it yourself: https://github.com/christianchown/easyrouter
Firstly, I have a logged out section, where you would fill out a login form or click on a social login button to authenticate yourself.
I also have an authenticated section with tabs, a drawer and screens to visit.
For each of these application states, I have a different EasyRouter
instance. You could just as easily have a single EasyRouter
instance and manage the authenticated/unauthenticated stack yourself.
const authenticatedRoutes = {
intro: IntroScreen,
login: LoginScreen
};const unauthenticatedRoutes = {
home: HomeScreen,
settings: SettingsScreen,
profile: ProfileScreen
};...{authenticated ? (
<EasyRouter routes={authenticatedRoutes} initialRoute="intro" />
) : (
<EasyRouter routes={unauthenticatedRoutes} initialRoute="home" />
)}
The router object
Each screen component gets a router
object prop. This object has navigation functions topop()
, push.SCREEN()
, replace.SCREEN()
and reset.SCREEN()
along with the ability to read the stack
.
Each of the pop/push/replace/reset navigation functions can specify:
parameters
passed to the screen component, as well asanimation
— which comprise a transitiontype
(none, bottom, left, right, top, left-bottom, left-top, right-bottom, right-top and fade are all built-in), and an optionalduration
andeasing
So with a router
object, you navigate by:
router.push.ScreenToGoTo(
{param1: 'value1', param2: 'value2'},
{type: 'fade'},
);
Which would make ScreenToGoTo
fade in with param1
, param2
and router
in its props.
Adding UI — tabs and drawers etc.
React Native Easy Router itself, unlike most navigation libraries, does not have UI built in. The router component can be passed a router
callback function prop, which gets passed the router
object that is given to screens. This object can then be used by your component UI.
In the example App, I set this router object to state, and then pass it via props to UI components like a <Drawer>
or <Tabs>
<DrawerComponent
router={this.state.router}
>
<TabsComponent
router={this.state.router}
>
<EasyRouter
{...props}
router={router => {this.setState({router})}}
/>
</Tabs>
</Drawer>
Prop drilling like this isn’t the only option to give non-screen components access to navigation methods; the router
callback prop could just as easily store the router
object in a singleton, Context
or other store that components can access.
Redux integration
By supply the onStackChange
callback prop, you can use a changed navigation stack to dispatch actions or perform other reactive responses to navigation events: the router itself is not opinionated about how you handle navigation state.
<EasyRouter
{...props}
onStackChange={newStack => {
dispatch({type: 'SET_ROUTER_STACK', payload: newStack});
}}
/>
Custom transitions
You can create custom transitions with an animations
prop. My authenticated route component uses an “effect” animation which scales as it fades in.
const animations = {
effect: [
// start
{
opacity: 0,
transform: [{scale: 0}],
},
// end
{
opacity: 1,
transform: [{scale: 1}],
},
// useNativeDriver
true
],
}<EasyRouter
{...props}
animations={animations}
/>// in some other componentthis.props.router.push.Screen({}, {type: 'effect'});
Animating UI to match navigation transitions
To update your UI in sync with navigation, use the onBeforeStackChange
prop — it gets passed the animation object used by the router, as well as the starting and ending navigation stacks. I use it in my <Tabs>
component.
<Tabs
transition={this.state.transition}
from={this.state.from}
to={this.state.to}
>
<EasyRouter
onBeforeStackChange={(transition, oldStack, newStack) => {
this.setState({
transition,
from: oldStack[oldStack.length-1].route,
to: newStack[newStack.length-1].route
})
}}
/>
</Tabs>
Shouldn’t the library support nested routers and have built-in drawers, tabs, modals, title headers and so on, like other navigation libraries?
No. The Unix philosophy says “do one thing, and do it well”, and this is a fine philosophy for a library to have.
Routing in React Native needn’t be complex, and with react-native-easy-router, it needn’t be for you, either.