Disclaimer – The contents of this article were accurate as of the published date. The world of React Native moves rather quickly, so please keep in mind that things may have changed since then.
React Native is a fantastic tool for building cross platform native applications. The ability to create apps for iOS, Android, and even other operating systems (MacOS, Windows, the web, and more) in one codebase typically allows for reduced cost, faster time to market, and increased maintainability when compared with writing separate codebases using Swift or Objective-C for iOS, and Kotlin or Java for Android.
Tacchi has been around since the early days of Apple’s App Store and Google’s Play Store, when cross platform toolsets left a lot to be desired when building high quality apps. So my team and I spent the first three quarters of the last decade creating mobile apps for our clients with separate Objective-C, Swift, and Java codebases. But in the last few years we have moved our mobile development work almost exclusively to React Native. This has brought the aforementioned benefits to our clients with great results.
However, React Native development comes with its own unique set of challenges, especially for those not coming from a background of building native mobile apps in the traditional way.
One of the first challenges when starting work with React Native is that of building out your app’s navigation, as React Native’s core library does not include any APIs for navigating between screens, and there are a number of competing open source options to choose from.
In particular, it’s important to ensure that navigating between your application’s various flows and screens is native-feeling to users of either platform, performant, easy to build, and maintainable.
In this article, I’ll provide a quick primer on how the native UI frameworks are constructed, introduce the main options available for handling navigation in React Native, and present the benefits and drawbacks for each, before providing some general advice on how to approach navigation for your React Native project.
In my examples, I’ll focus on the three most common navigation structures for organizing and presenting screens in mobile applications: stacks, tab bars, and modals.
A quick primer on native UI frameworks
This is a short introduction to the frameworks, but if you’re planning to work on a cross platform mobile app, you should absolutely ensure you’re familiar with Apple’s iOS Human Interface Guidelines and Google’s Design for Android, irrespective of the technology you’ll employ. These will teach you the important rules to follow when designing and building your app for a usable and native-feeling experience for both iOS and Android users.
iOS’ native framework for all UI, including navigation, is called UIKit. It provides a few key components for managing screen-based interfaces:
UIViewController – This typically controls each screen that’s presented. It has a lot of lifecycle callbacks and methods related to presentation, visibility, and so on that a standalone view does not have.
UINavigationController – This controls a stack of UIViewControllers, and includes a UINavigationBar that typically shows a back button and other buttons.
UITabBarController – This controls the display of the UIViewControllers or UINavigationControllers that belong to the tabs in its UITabBar view.
In addition, presenting screens modally is handled by one of the methods provided by UIViewController.
Android’s core UI framework for managing screens and screen based navigation doesn’t really have a name, it’s just part of the android.app package (more guides here). There’s also the Material Design library and Android Jetpack, which build on top of the core framework to provide additional UI management.
Activity – Effectively like a UIViewController, managing a view and providing lifecycle callbacks and other methods for managing a screen. It often includes management and display of an App Bar (previously Action Bar)
Fragment – You can think of these as a subview of an Activity’s view, but one that also has its own lifecycle callbacks and other abilities. It allows for componentizing the view hierarchy in an Activity.
BottomNavigationView – The Android equivalent of a UITabBarController. This sits inside an Activity’s view and controls a number of Fragments, switching between them when the user taps a tab.
In a nutshell, Jetpack’s Navigation component provides tools for more easily constructing your navigation, but under the hood manages an Activity’s UI by controlling the display of Fragments within it. BottomNavigationView can integrate with Navigation so that it can be controlled by it.
You’ll notice that I didn’t mention an android equivalent of a UINavigationController. There isn’t really a direct equivalent, but stack navigation can be achieved using either a FragmentManager if you want to use one activity to manage a stack of fragments, or you can present new Activities one on top of another.
What are my options for navigation in React Native?
There are other options, such as React Native Router Flux and react-router-navigation, which I won’t go into in this article as under the hood they use React Navigation anyway. Or of course you could draw your own UI entirely from scratch using something like React Router Native! However I wouldn’t recommend it for anything more than a simple UI driven application as it provides no UI components, leaving you to build your app’s UI with no sensible defaults such as tab bars, navigation (or top) bars, modals, and so on.
Which is more popular, and why?
React Navigation is currently more popular, if you consider its 16.9k vs React Native Navigation’s 10.9k stars on GitHub (at the time this article was written), and what we currently see in our interactions with Tokyo’s React Native developer community.
React Navigation is more heavily promoted by the core React Native team, which may partly explain its popularity over React Native Navigation.
In addition, React Navigation is easier to start using, especially for beginners or those coming from a non-mobile development background, due to React Native Navigation requiring editing of the native Xcode and Gradle projects and Objective-C and Java files, which is somewhat complex and can be painful. React Navigation on the other hand simply needs to be included in your package.json. I’ll explain the reasons for this difference later on.
React Native Navigation also cannot be used with Expo, a popular tool for quickly and easily creating new React Native projects, as Expo does not provide access to native project files unless you eject your project. React Navigation is recommended by the Expo team.
However, it’s worth noting that React Native Navigation is being watched more, while having fewer forks. The higher number of watchers may suggest a potentially higher number of engaged developers using it on important projects, and the smaller number of forks as a ratio suggests a little less need to make customizations or fixes to the codebase.
You’ll also note that they have a similarly large number of open issues, which is a little concerning, and indicative of the challenges and complexity they both face in their chosen approach.
What’s different about how they work?
The main difference between the two libraries is how they draw the UI elements you see onscreen, and the way the navigation between the screens is managed.
Let’s consider a very simple example application, consisting of a tab bar, containing a stack navigator in its first tab, and a screen that presents another screen modally in the second tab. We’ll focus on iOS to keep the length of this article in check.
Therefore there are no UIViewControllers, UINavigationControllers, UITabBarControllers, Activities or Fragments created by React Navigation. In fact the only UIViewController, Activity, or Fragment in an app using React Navigation is likely to be the one that’s created in any React Native application to host the root React Native view.
Let’s check our assertion by having a look at our app’s UI tree on iOS using Xcode’s UI hierarchy viewer:
As we can see, other than the root UIViewController that’s created by React Native (not React Navigation), there are no UIKit classes or subclasses to be seen. All the tabs and navigation bars you can see are simply RCTViews (React Native views, rendered natively as UIView subclasses).
React Native Navigation
This means that a tab bar would be a real UITabBar or BottomNavigationView, and new screens are UIViewControllers and Activities or Fragments.
In order to achieve this, React Native Navigation mounts a new React Native application for each screen that is displayed. An app using React Navigation will typically have only one React Native application running.
Let’s have a look at the same appbuilt using React Native Navigation:
As we can see, we now have three controller classes which extend UIKit framework classes: RNNBottomTabsController, a subclass of UITabBarController, which contains an RNNStackController, a subclass of UINavigationController, which contains an RNNcomponentViewController, a subclass of UIViewController.
But wait, there is another…
At this point, I should mention a recent additional library that adds a little complexity to the situation; React Native Screens integrates with React Navigation to make use of UIViewControllers and UINavigationControllers for presenting new screens. This brings it somewhere between React Navigation’s pure JS approach, and React Native Navigation’s framework wrapper approach. However, it’s still young, and doesn’t seem to support native tab bars yet.
So which should you use?
My strongest recommendation would be… both! You should spend some time working with each library to get a feel for the developer interface, the way the user interfaces work and feel for your particular design, and to obtain an idea of your app’s performance.
Prototyping out the general structure of your app’s main use case would be a good idea, especially using tab bars, stacks, and modals. It doesn’t take that long to get up to speed with each library, and you’ll learn a whole lot about an important aspect of React Native development. If your app is an important product that will be supported for years to come, a couple of days of prototyping is in my opinion a very sound investment.
However, if you really don’t want to do that, or are too pressed for time, that implies that your product is perhaps not so important to your company, or it’s a hobby project. Therefore, you might want to consider the following:
If you’re only just getting started with React Native, and don’t come from a background of traditional native mobile development, I’d suggest going with React Navigation as you can get up to speed with Expo very quickly, and it’s “good enough”.
If your product will be particularly complex, you’re a UX purist, you deeply care about getting the most optimal experience for each platform, or have a good amount of experience with traditional mobile development, I’d suggest giving React Native Navigation a shot. For experienced cross platform devs, you’ll find the paradigms might make more sense to you, and you’ll know instinctively how to leverage them. For complex products you’re likely to have better UI performance.
What do we use at Tacchi?
We’ve used React Navigation, React Native Router Flux and React Native Navigation across various client projects over the last few years. We even used NavigatorIOS in the days before React Navigation and React Native Navigation came out and it was subsequently removed!
But we’ve been using React Native Navigation exclusively on all of our projects for a while now. Unless there are strong reasons from a client or project perspective, or there’s a noticeable improvement in React Navigation and React Native Screens, we’ll likely continue.
The benefits of knowing that performance will not be adversely affected as apps grow, alongside knowing that, as UX purists, under the hood we’re making use of the amazing frameworks provided by the platform holders (Apple and Google) is a strong reason. We have the benefit of years of experience working with traditional iOS and Android development, which gives us a big advantage from this perspective.
Yes, React Native Navigation gives us some limitations due to the restrictions put in place from the underlying OS frameworks, but that’s actually a good thing in my opinion, forcing us to adhere to the aforementioned platform UI design guidelines to ensure we provide more usable and native-feeling apps.