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]
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
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';
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'
}
});
// 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'
}
});
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;
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 />;
}
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.
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' }} />
The changes are instantly reflected in the Expo client.
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'
}
});
Here is the output you are going to get after this step:
When the user presses the button on the Home screen, the navigator will direct the user to the Details
screen.
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'
}}
>
Here is the output:
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
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';
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.
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'
}
});
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()
});
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>;
}
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
}
}
}
}
}
`;
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
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:
Choose the option Debug Remote JS
. A debugger like below should appear in your default web browser.
Open the Console
tab from the Developer Tools
of the web browser. You are going to get the following result.
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'
}
});
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'
}
});
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';
//...
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>
Here is the output after this step:
You are also going to see the initial loading screen.
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>
);
}
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'
}
});
On visiting the details of any coin in the list, the following is going to be displayed.
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.