How to Set Up and Use Navigators in React Native

Aman Mittal - Jul 17 '20 - - Dev Community

React Navigation released its 5th stable version of the library a few months back. Even though the lib is just a little over two years old, it is without a doubt one of the most popular navigation solutions in React Native apps that also has support for the Expo SDK.

The major highlight of this new release version is that routing of screens is now based on component configuration.

In this tutorial, let's take a look at how to set up and use a stack navigation pattern between two screens, and pass data from one screen to another. The data is going to be fetched from a third-party API using GraphQL query language. This is possible using the Apollo client, which allows us to fetch results from a REST endpoint.

Prerequisites

To follow this tutorial, please make sure you have the following installed on your local dev environment:

  • Node.js version >= 12.x.x installed
  • JavaScript/ES6 basics
  • Have access to one package manager such as npm or yarn
  • expo-cli version installed or use npx

The demonstrated example is based on Expo SDK 38.

Install dependencies

To start, generate a new Expo project with a blank template by running the following command in a terminal window:

npx expo init [Project Name]

# after the project directory has been generated

cd [Project Name]
Enter fullscreen mode Exit fullscreen mode

Then, install the dependencies below for the react-navigation library to work. The first command is going to install the core packages of react-navigation. These core packages are used by navigators to create the navigation structure in the app.

The second command uses expo install instead of npm install or yarn add. The reason is that expo is going to install the version of the libraries mentioned that are compatible with the Expo SDK. This second command installs the peer dependencies that the react-navigation library depends on.

yarn add @react-navigation/native @react-navigation/stack

# use expo install for Expo projects only
expo install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view
Enter fullscreen mode Exit fullscreen mode

Do note that the package @react-navigation/stack is only required to install when you are going to use the Stack navigation pattern in the app. For example, if you are just going to use tab navigation, you are going to install a different package as shown here.

What is a stack navigator?

The stack navigation pattern allows transitioning from one screen of the app to another while managing the navigation history. If the app uses only one stack navigator, then it is conceptually similar to how a web browser handles navigation state. The app pushes and pops screens from the navigation stack as users interact with it, and this results in the user seeing different screens.

Start by creating a new directory src/navigation/ and inside it create a new file called MainStackNavigator.js with the following import statements.

import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
Enter fullscreen mode Exit fullscreen mode

From the above snippet, the NavigationContainer is a component that manages the navigation tree. It contains the navigation state and it has to wrap the structure of all navigators.

The createStackNavigator is a function that is used to implement a stack navigation pattern. This function returns two React components: Screen and Navigator that help to configure each component screen.

Since the app does not have any screen components for now, in the next section let us create both the screens the app requires using some mock data to display.

Create app screens

Start by creating two screen component files called Home.js and Details.js inside the directory src/screens/ with the following code snippets:

// src/screens/Home.js
import React from 'react';
import { StyleSheet, View, Text } from 'react-native';

export default function Home() {
  return (
    <View style={styles.container}>
      <Text style={styles.text}>Home Screen</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#ebebeb'
  },
  text: {
    color: '#333',
    fontSize: 24,
    fontWeight: 'bold'
  }
});
Enter fullscreen mode Exit fullscreen mode
// src/screens/Details.js
import React from 'react';
import { StyleSheet, View, Text } from 'react-native';

export default function Details() {
  return (
    <View style={styles.container}>
      <Text style={styles.text}>Details Screen</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#ebebeb'
  },
  text: {
    color: '#333',
    fontSize: 24,
    fontWeight: 'bold'
  }
});
Enter fullscreen mode Exit fullscreen mode

The stack navigator needs screen components as routes. These routes are available in the form of screen components. Import these two screen components inside the MainStackNavigator.js file and then create the Stack navigator function.

// other import statements

import Home from '../screens/Home';
import Details from '../screens/Details';

const Stack = createStackNavigator();

function MainStackNavigator() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen name="Home" component={Home} />
        <Stack.Screen name="Details" component={Details} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

export default MainStackNavigator;
Enter fullscreen mode Exit fullscreen mode

In the above snippet, there are two required props with each Stack.Screen. The prop name refers to the name of the route and the prop component specifies which screen to render at the particular route. The order of the screens matters, as the first screen defined in the stack navigator pattern is going to be the bottom of the stack.

Import this navigator inside the App.js component.

import React from 'react';
import MainStackNavigator from './src/navigation/MainStackNavigator';

export default function App() {
  return <MainStackNavigator />;
}
Enter fullscreen mode Exit fullscreen mode

To check if the configuration is working, start the development server with expo start and then open the app either on a real device or in a simulator as shown below.

React Native Navigators Screen 1

Specifying options for each screen in Stack Navigator

By default, the title shown on each screen is the same as the value provided on the name attribute for each screen component in the stack navigator. However, you can set the title of the screen. Let us change the title of the screen shown, from “Home” to “Crypto List”.

This is done by specifying the options on each screen as shown below. Open the MainStackNavigator.js file and the prop options on Stack.Screen for the Home component.

<Stack.Screen name="Home" component={Home} options={{ title: 'Crypto List' }} />
Enter fullscreen mode Exit fullscreen mode

The changes are instantly reflected in the Expo client.

React Native Navigators Screen 2

Navigating between two screens

Open screens/Home.js and add a button component that is going to navigate from the Home screen component to the Details screen when pressed.

Import TouchableOpacity from react-native and make sure to use the navigation prop passed to the Home screen. This prop is passed to every screen that is a route wrapped by the current Stack Navigator in the app.

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

export default function Home({ navigation }) {
  return (
    <View style={styles.container}>
      <Text style={styles.text}>Home Screen</Text>
      <TouchableOpacity
        style={styles.buttonContainer}
        onPress={() => navigation.navigate('Details')}
      >
        <Text style={styles.buttonText}>Go to Detail Screen</Text>
      </TouchableOpacity>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#ebebeb'
  },
  text: {
    color: '#333',
    fontSize: 24,
    fontWeight: 'bold'
  },
  buttonContainer: {
    backgroundColor: '#222',
    borderRadius: 5,
    padding: 10,
    margin: 20
  },
  buttonText: {
    fontSize: 20,
    color: '#fff'
  }
});
Enter fullscreen mode Exit fullscreen mode

Here is the output you are going to get after this step:

React Native Navigators Screen 3

When the user presses the button on the Home screen, the navigator will direct the user to the Details screen.

React Native Navigators Screen 4

Using screen options to modify the header

You can use the screenOptions prop to apply common styles to the header across the navigator. For example, in the code snippet below, let us set the properties, headerStyle, headerTintColor, and headerTitleStyle to change the background color of all screen headers as well as the color of the title on each screen.

  • headerStyle is a style object that can be used to set the background color of the header for the screen component;
  • headerTitleStyle is another style object that allows you to customize the title or the text of the header;
  • headerTintColor is the color property for both the back button and the title of the header.

Open the src/navigation/MainStackNavigator.js file to make these changes.

<Stack.Navigator
        screenOptions={{
          gestureEnabled: true,
          headerStyle: { backgroundColor: 'tomato' },
          headerTitleStyle: { fontWeight: 'bold' },
          headerTintColor: '#f8f8f8'
        }}
      >
Enter fullscreen mode Exit fullscreen mode

Here is the output:

React Native Navigators Screen 5

With the basic configuration of a stack navigator working, let us now fetch the data from a third-party API using the Apollo client in the next section.

Install Apollo dependencies

Let's install all the required dependencies to integrate the Apollo client and request the REST endpoint using GraphQL queries:

yarn add apollo-client apollo-cache-inmemory graphql-tag apollo-link-rest apollo-link graphql graphql-anywhere qs @apollo/react-hooks
Enter fullscreen mode Exit fullscreen mode

Configure the Apollo client in React Native

In this section, let us integrate the Apollo client so that we can fetch the data from the REST endpoint. Start by creating a new directory src/graphql and, inside it, also create a new file Client.js.

The apollo-client package along with apollo-cache-inmemory and apollo-link is a fully-featured GraphQL client that can be integrated into React or React Native apps. Let us import all three of them inside this file:

import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { RestLink } from 'apollo-link-rest';
Enter fullscreen mode Exit fullscreen mode

For demo purposes, the API endpoint will be used as a REST endpoint from CryptoCompare.com. Make sure, at this point, that you have access to the API Key (that is free at the time of writing this tutorial). Their API offers many endpoints for different use cases but we are going to fetch several top coins by their total volume across all markets in the last 24 hours.

React Native Navigators Screen 6

Add a RestLink for the REST API endpoint and pass headers, which is an object representing values to be sent as headers on the request:

const restLink = new RestLink({
  uri: 'https://min-api.cryptocompare.com',
  headers: {
    Authorization:
      'd251970548f7321b548d3fb61d58c1a456974ea02ba41437fc9bf711f4e89782'
  }
});
Enter fullscreen mode Exit fullscreen mode

Add the following configuration with the default cache and RestLink to complete the configuration of the Apollo client:

export const client = new ApolloClient({
  link: restLink,
  cache: new InMemoryCache()
});
Enter fullscreen mode Exit fullscreen mode

Now, open the App.js file to wrap the current stack navigator with ApolloProvider. This provider is similar to React's Context.Provider and places the Apollo client in the context. This makes them accessible to the Apollo client easily and from anywhere inside the component tree.

import React from 'react';
import MainStackNavigator from './src/navigation/MainStackNavigator';

import { ApolloProvider } from '@apollo/react-hooks';
import { client } from './src/graphql/Client';

export default function App() {
  return;
  <ApolloProvider client={client}>
    <MainStackNavigator />
  </ApolloProvider>;
}
Enter fullscreen mode Exit fullscreen mode

Writing your first GraphQL query

In this section, let us write a query to hook the Apollo client to fetch results from the REST API endpoint. However, the query is going to be made in GraphQL query language with the help of graphql-tag.

In the src/graphql/ directory, create a new file called Queries.js and import graphql-tag. Then, create a query to fetch data. This query is called FETCH_COIN_LIST and is defined using a template from the gql tag. Using the @rest directive, Apollo manages the parsing of the query from a REST endpoint to the GraphQL API.

import gql from 'graphql-tag';

export const FETCH_COIN_LIST = gql`
  query FetchCoinsList {
    coinsList
      @rest(type: "ListPayload", path: "/data/top/totalvolfull?tsym=USD") {
      Data @type(name: "DataPayload") {
        CoinInfo @type(name: "CoinInfoPayload") {
          Id
          Name
          FullName
        }
        DISPLAY @type(name: "DisplayPayload") {
          USD @type(name: "USDPayLoad") {
            PRICE
            OPENDAY
            HIGHDAY
            LOWDAY
            OPEN24HOUR
          }
        }
      }
    }
  }
`;
Enter fullscreen mode Exit fullscreen mode

Make a request to REST endpoint with the Apollo client

Open the file screens/Home.js and import the FETCH_COIN_LIST query as well as the useQuery hook from @apollo/react-hooks.

The React hook useEffect is used below to test that the endpoint is fetching data as per our needs. The data fetched is going to be displayed in a console statement.

The hook useQuery is used to request the API endpoint by referencing the query FETCH_COIN_LIST. After being called, it returns a result object with a set of properties. We only need two properties for now: loading and data. De-structure this query hook inside the Home component as shown below.

import React, { useEffect } from 'react';
import { useQuery } from '@apollo/react-hooks';

import { StyleSheet, View, Text, TouchableOpacity } from 'react-native';
import { FETCH_COIN_LIST } from '../graphql/Queries';

export default function Home({ navigation }) {
  const { loading, data } = useQuery(FETCH_COIN_LIST);

  useEffect(() => {
    console.log(data);
  }, []);

  return (
    <View style={styles.container}>
      <Text style={styles.text}>Home Screen</Text>
    </View>
  );
}

// ... rest remains same
Enter fullscreen mode Exit fullscreen mode

Sidenote: Don't forget that you should always protect your source code in commercial and enterprise apps to prevent tampering. For more details, follow this guide.


To see the result, make sure the expo start command is running from the terminal window. Then, go to the Expo client either on a real device or a simulator and open the developer menu on a Mac using:

  • if on iOS simulator, press Ctrl-Cmd-Z
  • if on Android emulator, press Cmd+M
  • if using a real device, just shake your device a bit

This is what the developer menu in an Expo client looks like:

React Native Navigators Screen 7

Choose the option Debug Remote JS. A debugger like below should appear in your default web browser.

React Native Navigators Screen 8

Open the Console tab from the Developer Tools of the web browser. You are going to get the following result.

React Native Navigators Screen 9

That's it! The Apollo integration is working and you can start displaying the data in the app.

Add an Activity Indicator while fetching results

The useQuery hook gives one property called “loading” that can be used to indicate on the device's screen when the query is in the process of fetching the result. Using the ActivityIndicator, a loading indicator can be displayed.

Import the ActivityIndicator component from react-native in Home.js.

export default function Home({ navigation }) {
  const { loading, data } = useQuery(FETCH_COIN_LIST);

  if (loading && !data) {
    return (
      <View style={styles.loadingIndicatorContainer}>
        <ActivityIndicator size="large" color="#fff" />
      </View>
    );
  }
  return (
    <View style={styles.container}>
      <Text style={styles.boldText}>Coins List</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#333',
    justifyContent: 'center',
    alignItems: 'center'
  },
  boldText: {
    color: '#fff',
    fontSize: 24,
    fontWeight: 'bold'
  },
  loadingIndicatorContainer: {
    flex: 1,
    backgroundColor: '#333',
    justifyContent: 'center',
    alignItems: 'center'
  }
});
Enter fullscreen mode Exit fullscreen mode

Display data in a list using FlatList

To display a list of items, let us create a separate component that can be reused for a different purpose if the scope of this app gets larger. Create a new directory called src/components and inside it place a new file ListItem.js.

This component is going to display the name, full name, and the price of the component, all inside a touchable button that is going to navigate to the Details screen you created earlier.

With some destructuring from the incoming props coin (which is going to be passed from Home.js), add a functional component called ListItem.

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

export default function ListItem(props) {
  const { coin, onPress } = props;
  const { CoinInfo, DISPLAY } = coin;
  const { FullName, Name } = CoinInfo;

  return (
    <TouchableOpacity
      style={styles.container}
      onPress={() => onPress && onPress(coin)}
    >
      <View style={styles.row}>
        <Text style={styles.text} numberOfLines={1}>
          {Name}
        </Text>
        <View style={styles.right}>
          <Text style={styles.text} numberOfLines={1}>
            {DISPLAY.USD.PRICE}
          </Text>
        </View>
      </View>

      <View style={styles.row}>
        <Text style={[styles.text, styles.name]} numberOfLines={1}>
          {FullName}
        </Text>
      </View>
    </TouchableOpacity>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20
  },
  active: {
    backgroundColor: 'rgba(255,255,255,0.05)'
  },
  row: {
    flexDirection: 'row',
    justifyContent: 'space-between'
  },
  right: {
    flex: 1,
    alignSelf: 'flex-end',
    alignItems: 'flex-end'
  },
  text: {
    color: '#FFFFFF',
    fontSize: 24,
    fontWeight: '500'
  },
  name: {
    color: 'rgba(255,255,255,0.5)',
    fontSize: 16,
    fontWeight: '300'
  }
});
Enter fullscreen mode Exit fullscreen mode

Now, import this component in the Home.js file. Also, import FlatList from react-native core.

// ...
import {
  StyleSheet,
  View,
  Text,
  FlatList,
  ActivityIndicator
} from 'react-native';

import ListItem from '../components/ListItem';
//...
Enter fullscreen mode Exit fullscreen mode

Next, add this FlatList component wrapped inside the root View component like below.

<View style={styles.container}>
  <FlatList
    contentContainerStyle={styles.contentContainerStyle}
    data={data.coinsList.Data}
    keyExtractor={item => item.CoinInfo.Id.toString()}
    renderItem={({ item }) => {
      return (
        <ListItem
          coin={item}
          onPress={() => navigation.navigate('Details', { coin: item })}
        />
      );
    }}
  />
</View>
Enter fullscreen mode Exit fullscreen mode

Here is the output after this step:

React Native Navigators Screen 10

You are also going to see the initial loading screen.

React Native Navigators Screen 11

Passing the data between two screens

Since all the props are being passed from the Home to the Details screen and the navigation pattern is working, let us set up the Details screen now. Once route.params is de-structured into an object, you can use the values from that object to get the data passed from the Home screen.

export default function Details(props) {
  const { route } = props;
  const { params } = route;
  const { coin } = params;
  const { CoinInfo, DISPLAY } = coin;
  const { FullName, Name } = CoinInfo;
  const { USD } = DISPLAY;
  const { PRICE, OPENDAY, HIGHDAY, LOWDAY, OPEN24HOUR } = USD;

  return (
    <View style={styles.container}>
      <View style={styles.header}>
        <Text numberOfLines={1} style={styles.text}>
          {Name} - {FullName}
        </Text>
        <Text style={styles.priceText} numberOfLines={1}>
          Price: {PRICE}
        </Text>
      </View>
      <View style={styles.statsContainer}>
        <View>
          <View style={styles.statRow}>
            <Text style={styles.stat} numberOfLines={1}>
              Open Day
            </Text>
            <Text style={styles.stat} numberOfLines={1}>
              {OPENDAY}
            </Text>
          </View>
          <View style={styles.statRow}>
            <Text style={styles.stat} numberOfLines={1}>
              Highest in a day
            </Text>
            <Text style={styles.stat} numberOfLines={1}>
              {HIGHDAY}
            </Text>
          </View>
          <View style={styles.statRow}>
            <Text style={styles.stat} numberOfLines={1}>
              Lowest in a day
            </Text>
            <Text style={styles.stat} numberOfLines={1}>
              {LOWDAY}
            </Text>
          </View>
          <View style={styles.statRow}>
            <Text style={styles.stat} numberOfLines={1}>
              Open in 24 hours
            </Text>
            <Text style={styles.stat} numberOfLines={1}>
              {OPEN24HOUR}
            </Text>
          </View>
        </View>
      </View>
    </View>
  );
}
Enter fullscreen mode Exit fullscreen mode

Also, add the corresponding styles to this component:

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff'
  },
  header: {
    flex: 30,
    justifyContent: 'center',
    alignItems: 'center'
  },
  text: {
    fontSize: 32,
    color: '#161616'
  },
  priceText: {
    fontSize: 24,
    color: '#161616'
  },
  statsContainer: {
    flex: 62,
    backgroundColor: '#161616'
  },
  statRow: {
    padding: 10,
    flexDirection: 'row',
    justifyContent: 'space-between'
  },
  stat: {
    color: '#fff',
    fontSize: 16,
    fontWeight: '500'
  }
});
Enter fullscreen mode Exit fullscreen mode

On visiting the details of any coin in the list, the following is going to be displayed.

React Native Navigators Screen 12

Conclusion

In this tutorial, we have discussed many strategies and properties that you can apply and implement in your Stack navigator. The first objective is to get familiar with the component-based configuration of the Stack Navigator in the latest version of the react-navigation library.

The second objective is also fulfilled, which is to use the REST endpoint and integrate the Apollo client into any API endpoint to query desired results in a React Native and Expo app and pass the data between two screens.

Finally, don't forget to pay special attention if you're developing commercial React Native apps that contain sensitive logic. You can protect them against code theft, tampering, and reverse engineering by following this guide.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .