SERIES: React Native (Step by Step) - Strongly-Typed Navigation with React Navigation 5.x and Typescript
Last time we added redux to our app. This time we will integrate React Navigation (v5.x at the time of writing), a very flexible and robust navigation/routing solution and the primary recommendation made by the expo team too.
With the v5 update, React Navigation got a major overhaul. They split up their old repository into several smaller ones and introduced many breaking changes but the current version is in a very good state right now and worth the effort.
In our example today, I will show you how to set up your basic routes and how to implement navigation actions and conditional navigation.
In the follow-up to this part, we will look at a very common authentification flow, nested sub navigations and a few tricks to make your life easier.
If you want to code along and did not follow the previous episodes, simply go to my GitHub Repository here and grab the code from the v0.2 tag.
We will start by installing the core library for react native via npm.
There is a list of packages we're going to need but as we are using the expo managed workflow, we can simply use the expo install command to install compatible versions of these libraries.
A small note, we won't be using all of these in this part of the series but I'm including them here because they are part of my regular setup anyway.
We will need 4 simple placeholder screens to showcase the navigation. You can simply create them under src/screens and add their names in a tag in a basic react component. That way we won't get any compiler warnings for missing module exports and can reference the correct files when we set up our routing.
Defining the basic routes
React Navigations can be nested and combined but for our main navigation, the one that will make whole page transitions, we are going to start with a simple stack.
Install the stack library in addition to the ones we grabbed earlier because we're going with a navigation stack for now.
A stack navigation works like a stack of card and comes with basic page transition animations based on your current device/os. Each time you navigate in a stack, you put a new "card" on top of the stack and when you navigate back, you remove the top-most card again. Last in, first out. Using a stack navigation will give you an app-like feel of responsiveness right out of the box.
Create a new file called routes.ts under src/routing and import the createStackNavigator function from react-navigation/stack. We will also export an enum of all the screens we register in our routing setup, which will allow us to refer to screens via MainRoutes.Home instead of using a regular string, something that will be helpful when working with typescript and will also be picked up by your IDEs autocompletion.
The second thing you see in this file is the type for all our screens. React navigation allows us to call screens with params. Defining them as undefined means, that this route does not expect any params on load while the "Home" route can be called with an optional boolean flag for the "update" attribute.
We then export our MainStack created with the createStackNavigator and our list of possible stack params.
A common example for route params would be something like an userID for a profile page or a sorting param in a list.
Building our Main Navigation
To build our MainNavigation component we are using a NavigationContainer from react navigation and map our Screen components with the navigation routes we defined. After importing our MainStack from './routes' we can use its Navigator and Screen components to organise our different screens/pages.
I'm using the attribute
headerMode="none" on the Navigator to disable the default topBar header that comes out of the box with the component.
Placing the main navigation
To use the new main navigation we just have to put it in our root project file, the App.tsx and throw out all content other than the redux provider.
When you restart your app now, everything should work again for a limited amount of "work". What we have right now is one big stack of screens. The user will start on the first screen in the stack, our splash screen, and has no means to navigate around.
About Navigation patterns
One of the most common navigation patterns is the so-called AuthFlow, where a different stack is used for the authentication of the user and the navigation of the actual app. I will show you a more complex AuthFlow in my next article but to show you the basics, we will mock a login with redux and use it to explain the principle.
While we are at it, go to your src/redux folder and rename the "demo/" folder to "ducks/", something I missed in my last article.
The idea behind ducks is to NOT have an actions.ts, middleware.ts and reducer.ts file for all your parts of the redux store but instead have one file with all parts of one slice combined.
If you want to know more about ducks and redux/reducks, I'll leave you with an article about the basic idea behind using ducks and the ducks proposal by Erik Rasmussen.
Create a new duck for our mock user and login code. For now, we will only include a setLogin action for toggling the login flag and a selector to access the stored value via a custom useState hook. Don't forget to register the new reducer in our rootReducer.ts and we are good to go.
With the new selectLogin selector, we can now adjust our navigation stack a bit.
As you can see, we are now using the selectLogin selector to set our isLoggedIn flag and use it in our Navigator to load a different stack of screens based on the login state. If the user is on the "Splash" screen and triggers a login, he will automatically be switched over to the first screen in the other stack and end up on the "Loading" screen.
In our SplashScreen.tsx file we can now set up a simple clickable screen that triggers the fake login.
The important thing to note here is that we are basically NOT triggering a navigation event with this (we'll come to that in a moment) but switching to a separate navigation stack. For this reason, we don't need to access the navigation params of our Navigator at all.
As I said, you can read more about this AuthFlow navigation pattern in the next article. Moving on...
Navigating our routes
When using React Navigation within our app, all screens that are direct children of the navigator have access to the "navigation" object. Using this with strong typing can be a bit tricky because the type for the navigation prop takes 2 generics, the param list object we defined earlier, and the name of the current route.
We don't want to rebuild this in every screen component that needs to access the navigation prop so I will add a types.ts file to the routing/ directory and import both the MainRoutes enum and the MainStackParamList that holds all our defined routes and their annotations.
We can now use this to annotate the navigation prop in our components. For our first screen after the login/stack switch, we will define a small delay before navigating to the home screen and display a simple "loading..." text.
This would usually be the place to check the version of your app against your servers, load user data and stuff like that. This process would then replace the setTimeout() we used here to fake a real app flow.
On our home screen, we will place a regular navigation button as well as a logout button dispatching a redux action to check if we get redirected to the auth stack when we set the login state to false again.
To wrap up this episode, the last thing I want to show you today is the goBack() function. On our SettingsScreen we will put a button that does not navigate to a specific screen but to the last screen on the stack prior to the SettingsScreen. This means that you could link to the settings from any page in the stack and when clicking the backlink, you would be taken back to the page you were before.
Today we learned how to annotate both our routes and our navigation prop.
Next time I will show you two more complex scenarios with react navigation including the useNavigation custom hook and how to trigger navigation events from redux middleware. We will build a better AuthFlow that mimics a real application (including some extra features), we will validate navigation events before they occur and I will show you how to implement a nested navigation to control parts of a page or modal window without moving away from the current screen.