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

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

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

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

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

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

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

Some words about me:

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 🎟