# 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>`s, `<ul>`s are different from `<FlatList>`s, and most web primitives differ from mobile ones — making 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 great for the web, but 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](https://reactnavigation.org/docs/en/web-support.html?ref=strv.ghost.io) support, it is still considered experimental and not yet widely used in production. You're likely to face issues related to history and query parameters. It also lacks TypeScript typings, which made me write some definitions myself, 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, etc.) do their 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 proper 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 gets a route as a first param and parameters as other arguments.

It can be used like this:

```javascript
import { useNavigation } from "../utils/navigation";
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 other routing utilities like `goBack` and `replace`.

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

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

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

const { path } = useRoute();

console.log(path);

// Logs:

// '/features/code-sharing' on 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 — with different paths and platform-specific implementations — that can be used for:

- Navigating with `useNavigation`
- Switching logic based on the current route with `useRoute`
- Specifying the `path` and extra data for 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 app. Built on top of `Link` from `react-router-dom` ([web version](https://github.com/ythecombinator/react-native-web-monorepo-navigation/blob/master/packages/components/src/Link/index.web.tsx?ref=strv.ghost.io)) and `TouchableOpacity` + `useNavigation` hook ([native version](https://github.com/ythecombinator/react-native-web-monorepo-navigation/blob/master/packages/components/src/Link/index.native.tsx?ref=strv.ghost.io)).

Usage example:

```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>;
```

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

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

By combining everything, you can traverse each [screen](https://github.com/ythecombinator/react-native-web-monorepo-navigation/tree/master/packages/components/src/screens?ref=strv.ghost.io) and see how `useRoute()`, `useNavigation()`, and `<Link />` can be used seamlessly across platforms.

---

## FUTURE STEPS

If asked how I'd approach future work, my steps would be:

1. Adding more utilities — e.g., a `<Redirect />` component for more declarative navigation.
2. Tackling edge cases on both platforms.
3. Reorganizing most of the navigation code into a dedicated library, leaving only `<Router />` and `utils/router` for app-specific configurations.

---

## CONCLUSION

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

While code sharing offers great benefits, shared cross-platform code should ideally include:

- Business Logic
- Config/translation files and static data not environment-specific
- API calls, authentication, and formatting

Other layers like routing should use the most suitable library for each platform (`react-router-dom` for web, `react-navigation` or similar for native).

Perhaps in the future, we’ll have a fully unified codebase, but for now, this approach seems the most practical.

---

## 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) at Reactive Conf. Highly recommended.
2. This was authored and used by Bruno Lemos, creator of [DevHub](https://devhubapp.com/?ref=strv.ghost.io), a multi-platform Github client with 95%+ code sharing. See [this tutorial](https://dev.to/brunolemos/tutorial-100-code-sharing-between-ios-android-web-using-react-native-web-andmonorepo-4pej?ref=strv.ghost.io) for more.
3. Known issues include:
  - Functionality-wide problems
  - URL query params not passed down (`here`)
  - Push navigation not working (`here` and `here`)
  - Params in URL encoding issues
  - Lack of TypeScript typings (`here`)
4. [React Router v5](https://reacttraining.com/blog/react-router-v5/?ref=strv.ghost.io) introduced structural improvements and hooks (v5.1).
5. [React Navigation v5](https://blog.expo.io/announcing-react-navigation-5-0-bd9e5d45569e?ref=strv.ghost.io) adopted a modern, hooks-first API.
6. A helpful post on declarative navigation: [<Redirect />](https://tylermcginnis.com/react-router-programmatically-navigate/?ref=strv.ghost.io).
7. For more, see [this talk](https://www.ythecombinator.space/talks/code-sharing-at-scale-one-codebase-for-web-mobile-and-desktop?ref=strv.ghost.io) about shared codebases, infrastructure, and design lessons.