React Native has the best “Buy One Get One Free” offer of any UI library. Write your code once and React Native gives you two Apps in return (Android and iOS). Other UI libraries, like Flutter, boast of a similar offer but they can’t compete on end user experience. React Native is unique because, by using the underlying platform-specific APIs, it produces Apps that look and feel the same as native ones.

Don’t forget to read the small print. The offer isn’t valid if you’re using React Navigation because it compromises the end user experience. React Navigation is a JavaScript-only navigation library so, as hard as it tries, it can’t ever replicate the UX of a native App. To take up the offer, you must use a library that calls into the platform-specific navigation APIs, like React Native Navigation.

React Native for Web upgrades the offer to “Buy One Get Two Free”. It runs the same code on the Web by rendering the React Native components as DOM elements. You get three Apps from a single codebase. But the small print this time reads “Not valid if using React Native Navigation”. React Native for Web can’t convert the React Native Navigation API into a DOM equivalent.

It’s a Catch 22. React Native Navigation honours the UX but doesn’t work with React Native for Web. React Navigation works with React Native for Web but compromises the UX.

The Navigation router comes to the rescue. It’s the first navigation library for React Native that uses the underlying platform-specific APIs and that works with React Native for Web. The first navigation library that doesn’t compromise the UX and that runs on the Web. The “Buy One Get Two Free” offer isn’t too good to be true after all.

Write Once, Navigate Anywhere

Taking advantage of the offer, I’ve written an example that produces three Apps from a single codebase. The example’s first scene displays a grid of colours for you to pick from and the second scene gives extra detail about your selected colour. The same Grid and Detail components render the scenes on all three platforms. With the Navigation router, you define states that represent the scenes. You map your components to scenes by attaching renderScene functions to the corresponding states .

const { grid, detail } = stateNavigator.states;

grid.renderScene = () => <Grid />;

detail.renderScene = ({ colour }) => <Detail colour={colour} />;

You navigate to the colour detail scene by passing the name of the ‘detail’ state along with the selected colour. It’s the same JavaScript function call on all three platforms but the Navigation router handles it differently on each one.

stateNavigator.navigate('detail', { colour: 'blue' });

On iOS, the Navigation router pushes a new UIViewController onto the native stack. The new UIViewController gets its screen content by calling into the App.js entry point. It passes down a crumb parameter that holds the index of the scene in the stack. The Scene component uses the crumb to find the corresponding ‘detail’ state and calls its renderScene function passing in the selected colour.

// App.js entry point

export default ({ crumb }) => (

<NavigationHandler stateNavigator={stateNavigator}>

<Scene crumb={crumb} />

</NavigationHandler>

);

On Android, the process is the same except that the Navigation router starts a new Java Activity instead of pushing a new Objective-C UIViewController .

On Web, the Navigation router has to work harder because the DOM doesn’t have a stack API. The NavigationMotion component is the Navigation router’s own implementation of a DOM-based stack. NavigationMotion accepts three style props , one for each of the different stages a scene can be in. It interpolates between the styles so you can fade in the colour detail scene as you fade out the colour grid scene.

render(

<NavigationHandler stateNavigator={stateNavigator}>

<NavigationMotion

unmountedStyle={{ opacity: 0 }}

mountedStyle={{ opacity: 1 }}

crumbStyle={{ opacity: 0 }}>

{({ opacity }, scene, key) => (

<div

key={key}

style={{ opacity }}>

{scene}

</div>

)}

</NavigationMotion>

</NavigationHandler>,

document.getElementById('content')

);

For the first time, you can get three Apps for the price of one codebase without compromising on the UX. An offer that’s too good to refuse.