# Universal Navigation Strategy for React Development

[Matheus Albuquerque](https://www.strv.com/blog/authors/ythecombinator) Frontend Engineer

---

## A BIT OF CONTEXT

I had only worked on an example React Native app before (uncompiled and unpublished). So when I joined this project, I didn’t actually know that much about React Native.

First thing I came across was [Expo](https://expo.io/?ref=strv.ghost.io) and its experimental web support, but I decided not to go for it mostly because I enjoy having control over the project stack and being aware of what's happening. I want to be able to customize the installation, install custom versions of modules and have more control over project dependencies.

I then heard of two other initiatives on Github: [ReactNative for Web](https://github.com/necolas/react-native-web?ref=strv.ghost.io) and [ReactXP](https://github.com/microsoft/reactxp?ref=strv.ghost.io). Both share similar goals but have different approaches. As the official documentation for ReactXP states:

> ReactXP is a layer that sits on top of React Native and React, whereas React Native for Web is a parallel implementation of React Native — a sibling to React Native for iOS and Android.

No time here to cover the differences between these two. Long story short, after checking out some technical blog posts and talks, we ended up going with ReactNative for Web.

After a bit of digging into articles and trying to implement each environment in its own realm, I found that for me, the best starting point was a great template called [react-native-web-monorepo](https://github.com/brunolemos/react-native-web-monorepo?ref=strv.ghost.io), which brings support for universal apps and gets a little help from [Yarn Workspaces](https://yarnpkg.com/lang/en/docs/workspaces/?ref=strv.ghost.io).

Before starting to implement this approach into your project, though, I suggest reviewing your requirements and checking whether these tools solve all of your needs.

---

## WHAT WE HAVE OUT THERE

Some popular routing solutions on the React.js ecosystem were not meant to support both DOM and native environments; `<div>`s are different from `<View>`, `<ul>`s are different from `<FlatList>`, and most of the web primitives are different from the mobile ones—which makes it difficult to come up with a universal solution. [@reach/router](https://reach.tech/router?ref=strv.ghost.io) is one example of web solutions that have opted not to face the challenges of supporting both environments.

As of now (February 2020), however, we have a few universal web/native formulas ready. But they all ended up not fully serving our needs. Examples are:

- [react-router](https://github.com/ReactTraining/react-router?ref=strv.ghost.io) is a great option for the web, but when on [mobile](https://reacttraining.com/react-router/native?ref=strv.ghost.io), it lacks screen transitions, modals, navbar, back-button support, and other essential navigation primitives.
- [react-navigation](https://reactnavigation.org/?ref=strv.ghost.io) suits mobile very well, but given its [web support](https://reactnavigation.org/docs/en/web-support.html?ref=strv.ghost.io), it is still considered experimental and has not yet been widely used in production. You're likely to face issues related to history and query parameters. Also, it lacks TypeScript typings—which made me write part of the definitions on my own, since TypeScript was a must-have for the project.

And this brings us to the next part!

---

## THINKING OF A SOLUTION

*The code from this post is available on GitHub: [ythecombinator/react-native-web-monorepo-navigation](https://github.com/ythecombinator/react-native-web-monorepo-navigation?ref=strv.ghost.io)*

When we started this journey, I admit one of the most puzzling things was not being able to figure out how exactly popular apps using React Native for Web (e.g., Twitter, Uber Eats, and all the others mentioned [here](https://github.com/necolas/react-native-web?ref=strv.ghost.io#react-native-for-web)) do the navigation, and how they deal with challenges like the ones I mentioned above.

So, we had to work it out on our own!

Our new solution was based on abstracting on top of the most recent releases of react-router-dom and react-navigation. Both have evolved a lot and now they seem to share a few goals which I consider key for properly doing navigation/routing in React:

- Hooks-first API
- Declarative way to implement navigation
- First-class types with TypeScript

We subsequently came up with a couple of utils and components which aim at a universal navigation strategy:

### [utils/navigation](https://github.com/ythecombinator/react-native-web-monorepo-navigation/blob/master/packages/components/src/utils/navigation?ref=strv.ghost.io)

Exposes two hooks:

- `useNavigation`: which returns a `navigate` function that takes a route as the first param and parameters as other arguments.

It can be used like this:

```javascript
import { useNavigation } from "../utils/navigation";

// Our routes mapping – we'll be discussing about this one in a minute

import { routes } from "../utils/router";

const { navigate } = useNavigation();

// Using the `navigate` method from useNavigation to go to a certain route

navigate(routes.features.codeSharing.path);
```

It also provides you with a few other known routing utilities, like `goBack` and `replace`.

- `useRoute`: which returns data about the current route (e.g., `path` and `params` passed to that route).

This is how it could be used to get the current `path`:

```javascript
import { useRoute } from "../utils/navigation";

const { path } = useRoute();

console.log(path);

// This will log:

// '/features/code-sharing' on the web

// 'features_code-sharing' on mobile
```

### [utils/router](https://github.com/ythecombinator/react-native-web-monorepo-navigation/tree/master/packages/components/src/utils/router?ref=strv.ghost.io)

This contains a `routes` object—which includes different paths and implementations for each platform—that can be used for:

- Navigating with `useNavigation`
- Switching logic based on the current route with `useRoute`
- Specifying the `path` and extra data of each route rendered by the `Router` component

### [components/Link](https://github.com/ythecombinator/react-native-web-monorepo-navigation/tree/master/packages/components/src/Link?ref=strv.ghost.io)

Provides declarative navigation around the application. Built on top of `Link` from `react-router-dom` on web and `TouchableOpacity` + `useNavigation` hook on mobile:

- [Web](https://github.com/ythecombinator/react-native-web-monorepo-navigation/blob/master/packages/components/src/Link/index.web.tsx?ref=strv.ghost.io):

```jsx
import { Text } from "react-native";
import { Link } from "../Link";
import { routes } from "../utils/router";

<Link path={routes.features.webSupport.path}>
  <Text>Check "Web support via react-native-web"</Text>
</Link>
```

- [Mobile](https://github.com/ythecombinator/react-native-web-monorepo-navigation/blob/master/packages/components/src/Link/index.native.tsx?ref=strv.ghost.io):

Just like `<Link>` from `react-router-dom`, it can be used like this:

---

### [components/Router](https://github.com/ythecombinator/react-native-web-monorepo-navigation/tree/master/packages/components/src/Router?ref=strv.ghost.io)

This is the router itself. On the web, it's basically a `BrowserRouter`, using `Switch` to pick a route. On mobile, it's a combination of both `Stack` and `BottomTab` navigators.

Combining everything we’ve mentioned, what you get is navigating through each [screen](https://github.com/ythecombinator/react-native-web-monorepo-navigation/tree/master/packages/components/src/screens?ref=strv.ghost.io) of the app and seeing how `useRoute()`, `useNavigation()`, and `<Link />` can be used regardless of the platform.

If asked about how I’d approach future work in a similar scenario, I'd say my next steps would be:

1. **Adding more utilities** – e.g., a `Redirect` component aiming at a more declarative navigation approach.
2. Tackling edge cases on both platforms.
3. Reorganizing most of the things inside a navigation library and leaving only the main `Router` component and `utils/router` to be written on the application side.

---

## CONCLUSION

My feeling is that web, mobile web, and native application environments all require a specific design and user experience—which, by the way, matches the mentioned *“Learn once, write anywhere.”* [React Native philosophy](https://reactjs.org/blog/2015/03/26/introducing-react-native.html?ref=strv.ghost.io).

Although codesharing is a great advantage to React and React Native, I’d say that shared cross-platform code should be mostly:

- Business Logic
- Config files, translation files, and most constant data—those that are not environment-specific
- API / Formatting; e.g., API calls, authentication, and formatting of request and response data

Other layers like routing should use a library most appropriate for the platform, i.e., `react-router-dom` for web, and `react-navigation` or similar for native.

Perhaps someday we’ll have a truly unified codebase, but for now, the technology isn't quite there, and the approach shared here seems the most suitable.

---

## FOOTNOTES

1. There's an amazing [talk by Evan Bacon on Expo for Web](https://www.youtube.com/watch?v=k1FdrhA2sCY&ref=strv.ghost.io) this year at Reactive Conf. If you haven't checked it out, I really recommend it.
2. This one was authored and is used by Bruno Lemos, the author of [DevHub](https://devhubapp.com/?ref=strv.ghost.io), a Github client that runs on Android, iOS, Web, and Desktop—with 95%+ code sharing between them. If you're interested in how he came up with this solution, check [this](https://dev.to/brunolemos/tutorial-100-code-sharing-between-ios-android--web-using-react-native-web-andmonorepo-4pej?ref=strv.ghost.io).
3. These issues include:
   - Functionality-wide
   - Query parameters from URL not passed down ([here](https://github.com/react-navigation/web/issues/45?ref=strv.ghost.io))
   - Pushing back not working ([here](https://github.com/react-navigation/web/issues/22?ref=strv.ghost.io) and [here](https://github.com/react-navigation/web/issues/41?ref=strv.ghost.io))
   - Some params pushed from one route to the other for convenience being encoded to the URL
   - Developer-experience-wide
   - Lack of TypeScript typings ([here](https://github.com/react-navigation/web/issues/34?ref=strv.ghost.io))
4. [React Router v5](https://reacttraining.com/blog/react-router-v5/?ref=strv.ghost.io) focused mainly on improvements. Version 5.1 introduced hooks that we could use.
5. [React Navigation v5](https://blog.expo.io/announcing-react-navigation-5-0-bd9e5d45569e?ref=strv.ghost.io) also brought a modern, hooks-first API.
6. There's a good post about declarative navigation with `<Redirect />` [here](https://tylermcginnis.com/react-router-programmatically-navigate/?ref=strv.ghost.io).
7. If interested, check out [this talk](https://www.ythecombinator.space/talks/code-sharing-at-scale-one-codebase-for-web-mobile-and-desktop?ref=strv.ghost.io) on sharing code across web, mobile, and desktop, including lessons from project setup, infrastructure, components, and styling.