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.

Simple unauthenticated route

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 as
  • animation — which comprise a transition type (none, bottom, left, right, top, left-bottom, left-top, right-bottom, right-top and fade are all built-in), and an optional duration and easing

So with a routerobject, 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 component
this.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.