React Native

Redefining Navigation in React Native: the Potential of expo-router

In the dynamic and constantly evolving world of mobile application development, choosing the right navigation tools can significantly impact the effectiveness and efficiency of your projects. Among these tools, expo-router has emerged as a strong contender. It aims to redefine our understanding of navigation in React Native applications by offering a unique approach to handling navigation. This approach includes several promising features that streamline the development process, bolster user experience and future-proof apps.

However, like all technologies, expo-router has certain drawbacks. This article delves into the advantages and drawbacks of expo-router, providing insights into its functionality and potential use cases. Additionally, we will share our plans concerning its adoption in our future projects.

A paradigm shift

react-navigation, a declarative approach

In React Navigation, you define your navigation structure using JavaScript objects and React components. Here's a simple example:

The declarative nature of React Navigation means that you don't have to manually manage the state of your navigation or handle transitions between screens. Instead, you define the structure of your navigation (with navigators and screens declaration) and the conditions under which certain screens should be displayed, and React Navigation handles the rest. This makes it easier to create dynamic navigators based on application state.

However, declaring each route and each navigator involves a lot of manual configuration and can lead to maintainability issues as your application grows. Here comes expo-router and its file-based routing paradigm. ⬇️

File-based routing with expo-router

With file-based routing, the framework automatically generates routes based on your project's file structure, removing the need for explicit route configuration. Your page's directory becomes an immediate reflection of your application's routes.

Becomes:

Bam tech react native expo router

Advantages

  • Simplicity: Routes are automatically generated based on the file structure, eliminating the need for explicit route configuration.
  • Readability: The file structure directly reflects the routing structure of the app, making it easy to understand the application's flow and open the right screen you are looking for.
  • Consistency: File-based routing enforces a consistent pattern for defining routes, leading to more predictable code.
  • Familiarity: It’s an old and commonly used paradigm that we can find in other frameworks like NextJS.

Drawbacks

  • Limited Flexibility: The structure of your URLs is tied to your project's file structure. This can be limiting in some cases, particularly if you need to create complex routing rules that don't map neatly onto a file-based system. (e.g., dynamic navigation based on a state machine where screens may not exist depending on the user)
  • Paradigm shift: There will be a learning curve to understanding and effectively using file-based routing.

expo-router API demo

If you want to learn how to use the API, check out the following article. It provides a hands-on example of building an app with two tabs using ++code>expo-router++/code>.

expo-router: Q&A

The documentation provides a detailed explanation of how to set up and use expo-router. If you are unfamiliar with the library, it is recommended that you read the documentation first as always.

During discussions about expo-router with some React Native developers, several questions arose and I tried to summarize them in this section.

What are the features of expo-router that make the difference?

Since It’s V1, expo-router:

At AppJS, Evan Bacon announced some new features for version 2 of expo-router, making it the best solution for developing a universal app. These include:

  • Enhanced TypeScript support with automatically typed routes
  • Static rendering on the web
  • SEO for native apps
  • expo-router/head
  • iOS handoff
  • Universal Links (URL schemes that allow a single link to either launch an app if it's installed on the device or directly to a website if the app is not installed, providing a seamless user experience across web and app platforms)
  • Lazy bundling with Async Routes
  • CSS in Metro

Does it provide type-safe navigation?

Type-safe navigation is a key feature to protect our apps from runtime crashes. react-navigation has come with numerous ways to tackle it over the years:

  • Type utils used to create types for ++code>route++/code> and ++code>navigation++/code> props passed to screens by the lib
  • The same type utils can be used to type the ++code>useNavigation++/code> and ++code>useRoute++/code> hooks thanks to generic types.
  • The ++code>routeParamList++/code> type can be declared, and ++code>useNavigation++/code> will use it to type your navigation. However, it's important to define your navigation from the root.
  • A new static API will be available in V7 and can ensure better type safety.

At the moment, expo-router V1 does not support Typescript. As a result, navigating and handling parameter errors can be difficult. However, Typescript support will be available with expo-router V2 and the release of Expo SDK 49. You can get a glimpse of how it works by checking out this example repository.

Beware that to make it work, you should set the environment variable ++code>EXPO_USE_TYPED_ROUTES=true++/code> as explained in the documentation.

A significant advantage of expo-router is that all navigation types are generated by the CLI and synchronized with the file system, eliminating the need for manual handling. The library also uses them automatically.

Does it provide safe runtime navigation?

While type-safe navigation is important, it is not always enough. In a previous article, I demonstrated a scenario using react-navigation that can lead to errors even when there are no TypeScript errors, due to a poor handling of the typing of ++code>useNavigation++/code>.

Bam tech react native runtime navigation

In this situation, if the code ++code>navigation.navigate("ScreenTwo")++/code> is called from ++code>ScreenThree++/code>, we’ll have a runtime error because ++code>ScreenTwo++/code> is not defined in the same navigator as ++code>ScreenThree++/code>. Instead of pushing arbitrary screen identifiers to the stack, a possible solution is to navigate from the root of the app, with the following invocation :

++code>navigation.navigate("ChildNavigatorOne", { screen: "ScreenTwo" });++/code>

URL-based navigation uses the exact same pattern to declare the navigation from the root of the application. expo-router always know exactly where to find the screen associated with a static URL, protecting you from the above issue.

For dynamic URLs modified at runtime, you still have a risk to build an invalid URL. Hopefully, you can implement a dynamic route that will match all unhandled navigation in expo-router.

Does it provide all mobile-specific navigations like a drawer or a bottom tab?

expo-router is built above react-navigation so you’ll have access to the same features. If you want to implement mobile-specific navigation like a drawer, you can reuse the navigator from @react-navigation/drawer as explained in the documentation. All properties available in react-navigation will be accessible, you don’t have to make any tradeoffs on customization. Other navigations are available :

Is it not more complex than React Navigation?

For basic navigation use cases, expo-router V2 will be simpler because you won’t have to define your navigators and their types, expo-router will handle that for you. You’ll be able to focus more on your application code.

However, for advanced use cases, you’ll have to come back to react-navigation APIs. For many production apps, knowledge about both libraries and paradigms will be required.

You’re telling me that I can get rid of my linking configuration?

Deep linking configuration with react-navigation has always been difficult to achieve. You need to manually configure a mapping between the paths and the screen they target and pass it to the linking property of the ++code>NavigationContainer++/code>. The maintenance of the linking config is error-prone.

Good news! expo-router provides automatic deep linking configuration and it works like a charm. However, react-navigation will offer the same feature in version 7.

bam react native navigation 7 alpha

Impact on your app’s architecture

File structure

By default, expo-router uses the ++code>app++/code> folder at the root of the Expo project to generate the navigation. At BAM, we have an old habit to declare all our app code inside an ++code>src++/code> folder and I would love to stick with it.

It’s also a concern that has been raised by Jamon Holmgren, as Ignite adopted a similar convention. Unfortunately, at the moment expo-router makes assumptions about the default path, so forcing it to look into another folder may lead to bugs. Fortunately, when expo-router becomes stable, it will be merged into the expo/expo repo and the path customization may become available.

bam react native expo router

Navigation isolation

In a previous article, I was advocating about the benefits of isolating the navigation from the rest of your app. One particular point has become more relevant than ever since the arrival of expo-router:

It’s easier to change your navigation library. Let’s be honest, if you use React Navigation for your app, you’ll probably stay with it for the app's lifetime. However, It’s more and more frequent today to generate a web app from a React Native codebase thanks to React Native Web. Unfortunately, React Navigation support for the web is still experimental. You may prefer to rely on a framework like NextJS and its routing features instead.

expo-router is the library that may make you ditch (at least partially because it still heavily relies on it) react-navigation. The migration from react-navigation to expo-router will be easier if your screens and your navigators are isolated from the rest of your codebase.

bam react native navigator

What happened to my App.tsx file?

In the expo-router setup documentation, you’ll be asked to configure your entry point ++code>index.js++/code> to contain only the following import:

++code>import "expo-router/entry";++/code>

This will export the root component of expo-router that will load all our app screens from our ++code>app++/code> folder. You may have noticed that we loose our famous ++code>App.tsx++/code> component where our app is configured.

bam react native app tsx

The solution of our problem relies on the layout feature available in expo-router. You can create a root layout that will set your App contexts and return the ++code>Slot++/code> component from expo-router, responsible for rendering the rest of the navigation.

Conclusion

expo-router is an exciting development in the React Native ecosystem, promising to redefine how we approach navigation in mobile applications. By automating the generation of routes based on file structure, expo-router enforces architectural best practices, paving the way for more intuitive and maintainable codebases. It also anticipates the future of web support, making it a forward-thinking choice for developers keen on universal application development.

However, like all technologies, expo-router is not without its drawbacks. As of now, we're still awaiting the stable release of V2, which promises enhanced TypeScript support and other critical features. The rigidity of its file-based system could be restrictive in some contexts. Moreover, for advanced use cases, you may find yourself needing to master both expo-router and react-navigation, which can lead to a steeper learning curve.

Despite these concerns, the advantages of expo-router are quite compelling, particularly when considering new projects. While its current version may lack certain features, the upcoming V2 seems promising and could be a game-changer when it's released and stabilized. In the worst-case scenario, its architecture and APIs should allow for a swift fallback to react-navigation if necessary. As for existing projects, there's no urgent need for migration, except potentially to the static API of react-navigation if it simplifies deep link configuration.

Développeur mobile ?

Rejoins nos équipes