Handling Mounting And Unmounting Of Navigation Routes In React Native — Smashing Magazine

Handling Mounting And Unmounting Of Navigation Routes In React Native

About The Author

Daniel Don is a software developer who loves sharing his knowledge and explaining seemingly difficult topics with relatable examples. He lives in Port Harcourt …

More about
Daniel ↬

Quick summary ↬

Often you need two different sets of navigation stacks for pre and post user authentication. Usually, to see more content, you have to be authenticated in some way. Let’s look at how to mount and unmount navigation stack based on a met condition in React Native.

In this article, we are going to walk through mounting and unmounting of navigation routes in React Native. An expected behavior of your app is that once the authentication condition is met, a new set of navigation routes are available only to logged-in users, while the other screens which were displayed before authentication is removed and can’t be returned to unless the user signs out of the application.

For security in your app, protected routes provide you with a way to only display certain information/content on your app to specific users, while restricting access from unauthorized persons.

We will be working with Expo for this project because it’ll help us focus on the problem at hand instead of worrying about a lot of setups. The exact same steps in this article could be followed for a bare React Native application.

You need some familiarity with JavaScript and React Native to follow through with this tutorial. Here are a few important things you should already be familiar with:

Project Setup And Base Authentication

If you’re new to using expo and don’t know how to install expo, visit the official documentation. Once the installation is complete, go ahead to initialize a new React Native project with expo from our command prompt:

You will be presented with some options to choose how you want the base setup to be:

In our case, let’s select the first option to set up our project as a blank document. Now, wait until the installation of the JavaScript dependencies is complete.

Once our app is set up, we can change our directory to our new project directory and open it in your favorite code editor. We need to install the library we will be using for AsyncStorage and our navigation libraries. Inside your folder directory in your terminal, paste the command above and choose a template (blank would work) to install our project dependencies.

Let’s look at what each of these dependencies is for:

npm install @react-native-community/async-storage @react-native-community/masked-view @react-navigation/native @react-navigation/stack react-native-screens react-native-gesture-handle

To start the application use expo start from the app directory in your terminal. Once the app is started, you can use the expo app from your mobile phone to scan the bar code and view the application, or if you have an android emulator/IOS simulator, you can open the app through them from the expo developer tool that opens up in your browser when you start an expo application. For the images examples in this article, we will be using Genymotions to see our result. Here’s what our final result will look like in Genymotions:

Folder Structures

Let us create our folder structure from the start so that it’s easier for us to work with it as we proceed:

We need two folders first:

Go ahead and create the two folders in your project directory.

Inside the context folder, create a folder called authContext and create two file inside of the authContext folder:

We will need these files when we start working with Context API.

Now go to the views folder we created and create two more folders inside of it, namely:

Now, we are not yet finished, inside the screens folder, create these two more folders:

If you followed the folder setup correctly, this is how your folder structure should look like at the moment:

More after jump! Continue reading below ↓

Too often web forms are inaccessible and painful to use. Let’s fix that! In , Vitaly will dive into everything web forms, from disabled buttons to inline validation. Online, and live. Aug 18 & 20, 2021.

Creating Our First Screen

Now let’s create our first screen and call it the welcomeScreen.js inside the preAuthScreens folder.

preAuthScreens > welcomeScreen.js

Here’s the content of our welcomeScreen.js file:

import React from 'react';
import { View, Text, Button, StyleSheet, TextInput } from 'react-native';

const WelcomeScreen = () => {

  const onUserAuthentication = () => {
    console.log("User authentication button clicked")
  }

  return (
    <View style={styles.container}>
      <Text style={styles.header}>Welcome to our App!</Text>
      <View>
        <TextInput style={styles.inputs} placeholder="Enter your email here.." />
        <TextInput style={styles.inputs} secureTextEntry={true} placeholder="Enter your password here.." />
<Button  title="AUTHENTICATE" onPress={onUserAuthentication} />
      </View>
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
  header: {
    fontSize: 25,
    fontWeight: 'bold',
    marginBottom: 30
  },
  inputs: {
    width: 300,
    height: 40,
    marginBottom: 10,
    borderWidth: 1,
  }
})

export default WelcomeScreen

Here’s what we did in the code block above:

First, we imported the things we need from the React Native library, namely, View, Text, Button, TextInput. Next, we created our functional component WelcomeScreen.

You’ll notice that we imported the StyleSheet from React Native and used it to define styles for our header and also our <TextInput />.

Lastly, we export the WelcomeScreen component at the bottom of the code.

Now that we are done with this, let’s get this component to function as expected by using the useState hook to store the values of the inputs and update their states anytime a change happens in the input fields. We will also bring import the useCallback hook from React as we will be needing it later to hold a function.

First, while we are still in the WelcomeScreen component, we need to import the useState and useCallback from React.

Now inside the WelcomeScreen functional component, let’s create the two states for the email and password respectively:

Next, we need to modify our <TextInput /> fields so that the get their value from their respective states and update their state when the value of the input is updated:

import React, { useState, useCallback } from 'react';
import { View, Text, Button, StyleSheet, TextInput } from 'react-native';

const WelcomeScreen = () => {
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')

  const onInputChange = (value, setState) => {
    setState(value);
  }
  return (
    <View>
      ...      
      <View>
        <TextInput
          style={styles.inputs}
          placeholder="Enter your email here.."
          value={email}
          onChangeText={(value) => onInputChange(value, setEmail)}
        />
        <TextInput
          style={styles.inputs}
          secureTextEntry={true}
          placeholder="Enter your password here.."
          value={password}
          onChangeText={(value) => onInputChange(value, setPassword)}
        />
        ...
      </View>
    </View>
  )
}
...

In the code above, here is what we did:

The next thing we need to work on is the onUserAuthentication() function with is called whenever the button for the form submission is clicked.

Ideally, the user must have already created an account and login will involve some backend logic of some sort to check that the user exists and then assign a token to the user. In our case, since we are not using any backend, we will create an object holding the correct user login detail, and then only authenticate a user when the values they enter matches our fixed values from the login object of email and password that we will create.

Here’s the code we need to do this:

...

const correctAuthenticationDetails = {
  email: 'demouser@gmail.com',
  password: 'password'
}
const WelcomeScreen = () => {
  ...

  // This function gets called when the `AUTHENTICATE` button is clicked
  const onUserAuthentication = () => {
    if (
      email !== correctAuthenticationDetails.email ||
      password !== correctAuthenticationDetails.password
    ) {
      alert('The email or password is incorrect')
      return
    }
      // In here, we will handle what happens if the login details are       // correct
  }

  ...
  return (
    ...
  )
}
...

One of the first things you’ll notice in the code above is that we defined a correctAuthenticationDetails (which is an object that holds the correct login details we expect a user to supply) outside of the WelcomeScreen() functional component.

Next, we wrote the content of the onUserAuthentication() function and used a conditional statement to check if the email or password held in the respective states does not match the one we supplied in our object.

If you would like to see what we have done so far, import the WelcomeScreen component into your App.js like this:

Open the App.js file and put this replace the entire code with this:

import { StatusBar } from 'expo-status-bar';
import React from 'react';
import { View } from 'react-native';
import WelcomeScreen from './views/screens/preAuthScreens/welcomeScreen';
export default function App() {
  return (
    <View>
      <StatusBar style="auto" />
      <WelcomeScreen />
    </View>
  );
}

Looking closely at the code above, you’ll see that what we did was import the WelcomeScreen component and then used it in the App() function.

Here’s what the result looks like of our WelcomeScreen looks like:

Now that we are done building the WelcomeScreen component, let’s move ahead and start working with Context API for managing our global state.

Why Context API?

Using Context API, we do not need to install any additional library into ReactJS, it is less stressful to set up, and is one of the most popular ways of handling global state in ReactJS. For lightweight state management, it is a good choice.

Creating Our Context

If you recall, we created a context folder earlier and created a subfolder inside of it called the authContext.

Now let’s navigate to the AuthContext.js file in the authContext folder and create our context:

context > authContext > AuthContext.js

The AuthContext we just created holds the loading state value and the userToken state values. Currently, in the createContext we declared in the code-block above, we didn’t initialize any default values here so our context is currently undefined. An example value of the auth context could be {loading: false, userToken: 'abcd}

The AuthState.js file holds our Context API logic and their state values. Functions written here can be called from anywhere in our app and when they update values in state, it is updated globally also.

First, let’s bring in all the imports we will need in this file:

context > AuthContext > AuthState.js

import React, { useState } from 'react';
import AuthContext from './AuthContext';
import AsyncStorage from '@react-native-community/async-storage';

We imported the useState() hook from ReactJS to hold our states, we imported the AuthContext file we created above because this is where our empty context for authentication is initialized and we will need to use it as you’ll see later on while we progress, finally we import the AsyncStorage package (similar to localStorage for the web).

AsyncStorage is a React Native API that allows you to persist data offline over the device in a React Native application.

In the code block above here’s what we did:

We declared two states for the userToken and isLoading. The userToken state will be used to store the token saved to AsyncStorage, while the isLoading state will be used to track the loading status (initially it is set to true). We will find out more about the use of these two states as we proceed.

Next, we wrote our onAuthentication() function. This function is an async function that gets called when the login button is clicked from the welcomeScreen.jsx file. This function will only get called if the email and password the user has supplied matches the correct user detail object we provided. Usually what happens during authentication is that a token is generated for the user after the user is authenticated on the backend using a package like JWT, and this token is sent to the frontend. Since we are not going into all of that for this tutorial, we created a static token and kept it in a variable called USER_TOKEN.

Next, we use the await keyword to set our user token to AsyncStorage with the name user-token. The console.warn() statement is just used to check that everything went right, you can take it off whenever you like.

Finally, we pass our onAuthenticated function as a value inside our <AuthContext.Provider> so that we can access and call the function from anywhere in our app.

screens > preAuth > welcomeScreen.js

First, import useContext from ReactJS and import the AuthContext from the AuthContext.js file.

import React, { useState, useContext } from 'react';
import AuthContext from '../../../context/authContext/AuthContext'
...

Now, inside the welcomeScreen() functional component, let’s use the context which we have created:

In the above code block, we destructured the onAuthentication function from our AuthContext and then we called it inside our onUserAuthentication() function and removed the console.log() statement which was there before now.

Right now, this will throw an error because we don’t yet have access to the AuthContext. To use the AuthContext anywhere in your application, we need to wrap the top-level file in our app with the AuthState (in our case, it is the App.js file).

Go to the App.js file and replace the code there with this:

import React from 'react';
import WelcomeScreen from './views/screens/preAuthScreens/welcomeScreen';
import AuthState from './context/authContext/AuthState'

export default function App() {
  return (
    <AuthState>
      <WelcomeScreen />
    </AuthState>
  );
}

We’ve come so far and we’re done with this section. Before we move into the next section where we set up our routing, let’s create a new screen. The screen we are about to create will be the HomeScreen.js file which is supposed to show up only after successful authentication.

Go to: screens > postAuth.

Create a new file called HomeScreen.js. Here’s the code for the HomeScreen.js file:

screens > postAuth > HomeScreen.js

For now, the logout button has a dummy console.log() statement. Later on, we will create the logout functionality and pass it to the screen from our context.

Setting Up Our Routes

We need to create three (3) files inside our navigation folder:

Once you’ve created these three files, navigate to the preAuthNaviagtor.js file you just created and write this:

navigation > preAuthNavigator.js

import React from "react";
import { createStackNavigator } from "@react-navigation/stack";
import WelcomeScreen from "../screens/preAuthScreens/welcomeScreen";

const PreAuthNavigator = () => {
    const { Navigator, Screen } = createStackNavigator();

    return (
        <Navigator initialRouteName="Welcome">
            <Screen
                name="Welcome"
                component={WelcomeScreen}
            />
        </Navigator>
    )
}
export default PreAuthNavigator;

In the file above, here’s what we did:

Let us do a similar thing for the postAuthNavigator.js file.

navigation > postAuthNavigator.js

import React from "react";
import { createStackNavigator } from "@react-navigation/stack";
import HomeScreen from "../screens/postAuthScreens/HomeScreen";
const PostAuthNavigator = () => {
  const { Navigator, Screen} = createStackNavigator();
  return (
    <Navigator initialRouteName="Home">
      <Screen
        name="Home"
        component={HomeScreen}
      />
    </Navigator> 
  )
}
export default PostAuthNavigator;

As we see in the code above, the only difference between the preAuthNavigator.js and the postAuthNavigator.js is the screen being rendered. While the first one takes the WelcomeScreen, the postAuthNavigator.js takes the HomeScreen.

To create our AppNavigator.js we need to create a few things.

Since the AppNavigator.js is where we will be switching and checking which route will be available for access by the user, we need several screens in place for this to work properly, let’s outline the things we need to create first:

Now, let’s go ahead and create our TransitionScreen.js file.

screens > TransitionScreen.js

Our transition screen is just a simple screen that shows loading text. We will see where to use this as we proceed in this article.

Next, let us go to our AuthState.js and write our checkAuthenticationStatus():

context > authContext > AuthState.js

import React, { useState, useEffect } from 'react';
import AuthContext from './AuthContext';
import AsyncStorage from '@react-native-community/async-storage';

const AuthState = (props) => {
    const [userToken, setUserToken] = useState(null);
    const [isLoading, setIsLoading] = useState(true);

    ...
    useEffect(() => {
        checkAuthenticationStatus()
    }, [])
    
    const checkAuthenticationStatus = async () => {
        try {
            const returnedToken = await AsyncStorage.getItem('user-toke             n');
            setUserToken(returnedToken);
            console.warn('User token set to the state value)
        } catch(err){
            console.warn(`Here's the error that occured while retrievin             g token: ${err}`) 
        }
        setIsLoading(false)
    }


    const onAuthentication = async() => {
        ...
    }

    return (
        <AuthContext.Provider
            value={{
                onAuthentication,
                userToken,
                isLoading,
            }}
        >
            {props.children}
        </AuthContext.Provider>
    )
}
export default AuthState;

In the code block above, we wrote the function checkAuthenticationStatus(). In our function, here’s what we are doing:

Now that we have our function, it is time to go back to our AppNavigator.js and write the code for mounting a particular stack navigator based on the authentication status:

navigation > AppNavigator.js

First, we will import all we need for our AppNavigator.js.

import React, { useEffect, useContext } from "react";
import PreAuthNavigator from "./preAuthNavigator";
import PostAuthNavigator from "./postAuthNavigator";
import { NavigationContainer } from "@react-navigation/native"
import { createStackNavigator } from "@react-navigation/stack";
import AuthContext from "../../context/authContext/AuthContext";
import TransitionScreen from "../screens/TransitionScreen";

Now that we have all our imports, let’s create the AppNavigator() function.

Next, we will now go ahead to write the content of our AppNavigator() function:

import React, { useState, useEffect, useContext } from "react";
import PreAuthNavigator from "./preAuthNavigator";
import PostAuthNavigator from "./postAuthNavigator";
import { NavigationContainer } from "@react-navigation/native"
import { createStackNavigator } from "@react-navigation/stack";
import AuthContext from "../../context/authContext/AuthContext";
import TransitionScreen from "../screens/transition";

const AppNavigator = () => {
    const { Navigator, Screen } = createStackNavigator();
    const authContext = useContext(AuthContext);
    const { userToken, isLoading } = authContext;
    if(isLoading) {
      return <TransitionScreen />
    }
    return (
    <NavigationContainer>
      <Navigator>
        { 
          userToken == null ? (
            <Screen
              name="PreAuth"
              component={PreAuthNavigator}
              options={{ header: () => null }}
            />
          ) : (
            <Screen 
              name="PostAuth"
              component={PostAuthNavigator}
              options={{ header: () => null }}
            />
          )
        }
      </Navigator>
    </NavigationContainer>
  )
}

export default AppNavigator

In the above block of code, here’s an outline of what we did:

Now we’ve set up our AppNavigator.js. Next, we need to pass our AppNavigator into our App.js file.

Let’s pass our AppNavigator into the App.js file:

App.js

Let’s now see what our app looks like at the moment:

Here’s what happens when you supply an incorrect credential while trying to log in:

Adding The Logout Functionality

At this point, our authentication and route selection process is complete. The only thing left for our app is to add the logout functionality.

The logout button is in the HomeScreen.js file. We passed an onLogout() function to the onPress attribute of the button. For now, we have a simple console.log() statement in our function, but in a little while that will change.

Now, let’s go to our AuthState.js and write the function for logout. This function simply clears the AsyncStorage where the user token is saved.

context > authContext > AuthState.js

The userSignout() is an asynchronous function that removes the user-token from our AsyncStorage.

Now we need to call the userSignout() function in our HomeScreen.js any time the logout button is clicked on.

Let’s go to our HomeScreen.js and use ther userSignout() from our AuthContext.

screens > postAuthScreens > HomeScreen.js

import React, { useContext } from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';
import AuthContext from '../../../context/authContext/AuthContext'

const HomeScreen = () => {
  const { userSignout } = useContext(AuthContext)
  
  const onLogout = () => {
    userSignout()
  }
  return (
    <View style={styles.container}>
      <Text>Now you're authenticated! Welcome!</Text>
 <Button title="LOG OUT" onPress={onLogout} />
    </View>
  )
}
...

In the above code block we imported thee useContext hook from ReactJS, then we imported our AuthContext. Next, we destructured the userSignout function from our AuthContext and this userSignout() function is called in our onLogout() function.

Now whenever our logout button is clicked, the user token in our AsyncStorage is cleared.

Voila! our entire process is finished.

Here’s what happens when you press the back button after you’re logged in:

Here’s what happens when you press the back button after logging out:

Here are some different behaviors we notice when using this pattern in our navigation stack switching:

Conclusion

In many Apps, authentication is one of the most important parts because it confirms that the person trying to gain access to protected content has the right to access the information. Learning how to do it right is an important step in building a great, intuitive, and easy to use/navigate the application.

Building on top of this code, here are a few things you might consider adding:

Here are also some important resources I found that will enlighten you more about authentication, security and how to do it right:

Resources

Smashing Editorial
(ks, vf, yk, il)

This content was originally published here.