SERIES: React Native (Step by Step) — Nested Navigation, Persistent Redux Store and Returning Users

Last time we had a look at a more complex navigation flow using the AuthFlow Pattern. This time we will look into different types of nested navigations and adjust our main navigation flow a bit more to take care of persisted user data/login state and such.

Nested Navigation inside a screen

Here is a quick preview of what we will start with today.

For our first trick, we need a new type of navigation so we will grab it from npm real quick. I’ll show you how to set up a nice and pre-styled material design tab navigation for our settings page.

  • @react-navigation/material-top-tabs
  • react-native-pager-view
  • react-native-tab-view
expo install @react-navigation/material-top-tabs react-native-pager-view react-native-tab-view

On our SettingsScreen.tsx we will use a new component with some simple mocked pages, including one really long scrollable list. We also import our createMaterialTopTabNavigator element from the material-top-tabs package to define the navigator we will implement within our page.

A small detail you will need to keep in mind when nesting navigators is that each navigator keeps track of its own history, params and options.

When navigating, React Navigation will search in its current scope for a navigator. If none is present, it will bubble up a level to the parent components until it finds a navigator to perform navigation actions on. For additional details, you should take the time (5 minutes tops) to read the official documentation.

When we add our new Settings component to our SettingsScreen, we now have a nested in-page navigation element that works independently from its parent navigation container.

Nested Navigation inside a navigator as a screen

Instead of placing a navigatable component within your screen, you can also use it as a screen on its own. Let’s say our home screen consists of 3 different pages you can switch between.

For this example, we will use a more bare bone basic bottom-tabs navigator.

expo install @react-navigation/bottom-tabs

For this example, I will simply copy and adjust our existing home screen. Feel free to flesh it out a bit more to get something less copypasta and more life-like, if you want. You could turn them into news, home and category screens for your app or something along those lines.

Now go to the routes.ts file and add our new routes. We’ll define a new enum for the HomeRoutes and use it to create our HomeTabsParamList as we did before with our MainStackParamList.

In the MainNavigation component, we can now create a new Navigator using our HomeTabs instead of the MainStack and add 3 screens to it, our existing home screen and the 2 new ones.

A bit further down, we replace the HomeScreen component with our new Home Navigator component and that’s it.

And that’s it, we now have a 3-tabs nested navigator without our main stack on the same level as the settings screen.

Adding Persistence to our Redux Store

You might want to keep some user data stored locally between sessions. For that, we can make use of redux-persist and react natives async storage.

I’ve already started writing a separate article about Redux, Persist and different way to consolidate and rehydrate state data including ways to migrate old persisted data when the app changed. For now, I’ll stick to the basics.

expo install redux-persist @react-native-async-storage/async-storage

For now, we want to make the user data persistent. This means that upon re-opening the app, the user email and logged in state will still be set so that we can skip the login screens.

Redux persist requires a few minor changes to our redux code and App file. First of all, we need to import AsyncStorage as well as persistStore and persistReducer in our central redux file.

The persist config contains a key, our storage engine and a whitelist of the reducers we want to rehydrate when we restart the app. Key and the storage engine are required while the whitelist attribute is optional. We could go with a whitelist (mark what we keep), blacklist (mark what we don’t want to keep) or neither and keek all the data.

I prefer whitelisting for this because I’m more in control when I later add more and more reducers that I might never need on the next startup. Persisted data should always serve a real purpose and should be chosen wisely. Hence, whitelists are the way to go.

When we set up our store with configureStore, we need to make 2 small changes. Instead of our rootReducer, we now use the persistedReducer (containing our rootReducer) and as we are currently using the serializableCheck middleware that comes with Redux Toolkit, we need to put a few things on the blacklist so they don’t get checked. The actions you see here under ignoredActions are all imported from redux-persist as well.

In addition to our regular redux code, we will also export a persistor, a higher-order function using our store. You will see in a moment, what we need it for.

Moving on to our central entry point, we need to make a small adjustment to our code. We are importing a PersistGate from the react specific module of redux-persist and our new persistor and we will wrap everything inside our Redux Store Provider in the PersistGate component.

The PersistGate will serve as a gatekeeper that will not let the user pass until the state has been rehydrated with the stored values. This will prevent a lot of bugs and issues to occur from working with an incomplete or wrong state in the redux store. Additionally, you can register a component with the PersistGate using the loading={} attribute. This component will b shown until the store is ready to be used again.

Persisted Login in our Navigation Flow

For our final adjustments to the authentification and navigation flow, I will briefly skip over some simple parts that are mostly variations of what we did before.

Our plan is as follows. A new user goes through the whole authFlow as before while a logged-in user skips a few screens.

  • => start with Auth Stack
  • Splash
  • AppCheck
  • Login/SignUp (skip when already logged in)
  • AppLoading
  • => switch to App Stack
  • Home / Settings / etc

A new slice for our store

First of all, we need to add a new reducer to our store. For now, we will only put the app state (app finished all checks and loading and is now properly running) here but it is important to NOT put this on the whitelist. We want this to be reset at each start of the app.

Then we will replace the navigation and useEffect from the AppLoadingScreen and replace it with our new redux action in a useFocusEffect. The idea is to check all user data on this screen and once everything is checked and loaded, the user is switched over to the other navigation stack.

In our MainNavigation, we need to make 2 smaller adjustments. For one we will now put the AppLoading screen into the auth stack (last screen). The second change is to switch from selectLogin to selectIsRunning.

On our login and signup screens, we will add a watcher to the login state and trigger a navigation action when isLoggedIn changes and is set to true.

Navigating based on state

On our AppCheck, we will take a look at the login state for our user and direct signed in users directly to the AppLoading while everyone else will see the sign in next.

Keep in mind that we need to use a useCallback hook here because otherwise our getRoute would be recreated on every render and trigger our other useCallback that is responsible for our login.

The last thing to do now is to adjust our userReducer so that when a user is logged our not only his login status changes but also the appStatus. This will cause him to be switched back to the AuthFlow navigation stack where he starts on the splash screen again and can log in or sign up as a different user.

Wrapping Up

I think we’ve covered a lot of ground today and learned a few useful techniques when working with navigation. Next time we will look into styling react native components with … styled-components, one of a few good solutions for writing CSS in JS. A few years back this sentence would have made my skin crawl but bear with me and I will show you how to do so and NOT feel like betraying years of Frontend Developer Experience.

Some words about me:

If you want to see more of my work and progress, feel free to follow me and check out my other articles. If you clap feverishly for the articles you like most, it will be easier for me to decide which directions to pursue in following articles so use your ability to cast a vote for future content.

I’m also currently working on other series covering complex React Native Setups using Typescript and scalable apps with Redux, where I’ll go into details about how and why I do stuff the way I do as well as some articles on my experience in building games for web and mobile with React.

Here are some of my recent topics:
- React Quick Start with Typescript, Redux and Router
- Linting/Prettier with Typescript
- Redux + Toolkit with Typescript
- Spread & Rest Syntax in Javascript
- clean and simple Redux, explained
- Game Theory behind Incremental Games
- Custom and flexible UI Frames in React Native

And if you feel really supportive right now, you can always support me on patreon, thus allowing me to continue to write tutorials and offer support in the comments section.

I’m a Web / App Developer & father 👨‍👩‍👧 doing freelance and part-time agency work since 2003, 💻 building stuff on the side 🕹 and attending conferences 🎟

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store