- Implement stack navigation in a React Native app.
- Use the navigation prop to pass parameters between routes.
- Add modals to the app.
Not every mobile app will be suited for tab navigation (as discussed in the previous lab). React Navigation provides support for stack navigation. This is more akin to the global history stack in a browser (e.g., when a user navigates to a new page in a browser, that route is pushed to the top of the history stack). React Native doesn't have a history stack built in, so we must use packages like React Navigation to handle this routing.
This is a standard React Native project. If you haven't already, follow the Expo Go Quickstart setup guide in the React Native docs. Run npm install and then run npm run ios.
We're building a simple shopping app that shows a products page. Clicking on a product will take users to a details page, where they'll be able to select Add to Cart. Selecting this button opens up a confirmation modal.
Note: You won't see anything in the app until we add the stack navigator and screens.
-
First, install React Navigation by running
npm install @react-navigation/native. -
Because this is an Expo-managed project, we'll use Expo to install the necessary peer dependencies:
npx expo install react-native-screens react-native-safe-area-context. (In a bare React Native project, we can install these dependencies usingnpm install.) -
Lastly, install React Navigation's stack library:
npm install @react-navigation/native-stack.
-
In
App.js, import theNavigationContainerfrom@react-navigation/native. This is a named export. -
It's necessary to wrap your app in the
NavigationContainercomponent in order for React Navigation to work. InApp.js, add theNavigationContainerwithin theCartContext.Providercomponent.
Click here for the coded answer
import { NavigationContainer } from '@react-navigation/native';return (
<CartContext.Provider value={{ cart, setCart }}>
<NavigationContainer>
</NavigationContainer>
</CartContext.Provider>
);- Next, import the
createNativeStackNavigatorfunction from@react-navigation/native-stack. Outside of theAppcomponent, call this function and assign it to a variableStack.
Click here for the coded answer
import { createNativeStackNavigator } from '@react-navigation/native-stack';
const Stack = createNativeStackNavigator();
export default function App() {
// ...
}-
Like the
createBottomTabNavigatorfunction from the previous React Navigation lab,createNativeStackNavigatorreturns an object withNavigatorandScreencomponents. TheNavigatorcomponent creates the stack navigator; it must be the parent of theScreencomponents. TheScreencomponent creates the individual screens in your app. You can have as manyScreencomponents as needed. (Later, we'll use a newGroupcomponent to group our screens together.) -
Add the
Stack.Navigatoras a child of theNavigationContainer. Then, add twoStack.Screencomponents as children of theStack.Navigator. Add aStack.Screenwith anameprop ofProductsand pass theProductsScreencomponent as itscomponentprop. Do the same for theDetailsScreenand name itDetails. Check out the docs for more information.
In the previous React Navigation lab, we had to pass extra props to our screens so we provided the components as child callback functions. Because we'll be passing parameters around with the
navigationprop, we don't need any extra props! Just pass the screen components to thecomponentprop--no functions needed.
Click here for the coded answer
return (
<CartContext.Provider value={{ cart, setCart }}>
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Products" component={ProductsScreen} />
<Stack.Screen name="Details" component={DetailsScreen} />
</Stack.Navigator>
</NavigationContainer>
</CartContext.Provider>
);- Run the app. You should be able to see the
ProductsScreen. But how can you get to the details? Without tab navigation, we don't have handy tabs to click through the app, meaning that we'll have to handle the navigation ourselves. We'll tackle that in the next section!
-
On a web app, you'd use anchor (
<a>) tags to navigate. This tag would push the route to the top of the browser's history stack. We can use React Navigation's navigation prop to navigate to a new screen in a similar manner. By callingnavigation.navigateand providing it with the name of a screen, you're be able to navigate to a new screen. Thisnavigationprop is provided to every screen component. To use thenavigationobject in a component that isn't a screen, use theuseNavigationhook. -
We want to navigate to the details when we click the
ProductCard, so we'll need to give thePressablecomponent inProductCardanonPresshandler. InProductCard, write a function that callsnavigation.navigate. You'll need to import{ useNavigation }from@react-navigation/native. Thenavigation.navigatemethod takes two arguments: the name of the screen being navigated to and parameters to pass to the screen. Pass the screen name'Details'and the objectproductto thenavigation.navigatefunction. (This will be the sameproductpassed to eachProductCard.) Pass the function you write to theonPressprop of thePressablecomponent. -
In the app, click on a product card. You can successfully navigate to the
Detailspage! But you can't see any details. In the next step, we'll learn how to pull out the route's parameters!
Click here for an example answer
const ProductCard = ({ product }) => {
const navigation = useNavigation();
const onPress = () => {
navigation.navigate('Details', product);
};
return (
<Pressable onPress={onPress} style={styles.container}>
<Text style={styles.title}>{product.name}</Text>
<Text style={styles.text}>{product.price}</Text>
<Image source={product.image} style={styles.image} />
</Pressable>
);
};-
The
routeprop contains information about the current route. Like thenavigationprop,routeis passed as a prop to screen components. For other components, you can use theuseRoutehook to access therouteobject. Check out the React Navigation docs for more info. -
In the
DetailsScreen, destructureroutefrom the props passed to the component. Assign theproductvariable the value ofroute.params. -
Click through multiple products. You can now see the product details!
Click here for the coded answer
const DetailsScreen = ({ route }) => {
const { cart, setCart } = useContext(CartContext);
const product = route.params;
const onPress = () => {
setCart([...cart, product]);
};
return (
<View style={styles.container}>
<View style={styles.content}>
<Text style={styles.title}>{product.name}</Text>
<Image style={styles.image} source={product.image} />
<Text style={styles.text}>{product.description}</Text>
<Button color="blue" onPress={onPress} title="Add to Cart" />
</View>
</View>
)
};-
React Navigation also lets us create and navigate to modals. React Navigation handles all of the animations and opening/closing for us. All we have to do is provide the components! See the docs for more info.
-
When the
Add to Cartbutton in theDetailsScreenis clicked, we want to navigate to a confirmation modal confirming that the item was added to the user's cart. First, though, we need to add the modal. Back inApp.js, we'll need to use theGroupcomponent included inStackto separate out our screens.Groupgives us a little more granular control. Wrap theStack.Screencomponents forProductsandDetailsin aStack.Groupcomponent. -
Create a separate
Stack.Groupas a child of theStack.Navigator.Stack.Groupcan take ascreenOptionsprop, allowing for a wide range of customization. For this particularStack.Group, passscreenOptionsan object with a key ofpresentationand a value of'modal'. This tells React Navigation that the screens inside this group should present as modals. -
Within your second
Stack.Group, add aStack.ScreennamedConfirmationand give itConfirmationModalScreenas itscomponentprop. -
In the
DetailsScreencomponent, destructure thenavigationprop. In the existingonPressfunction, navigate to theConfirmationscreen usingnavigation.navigateand pass it theproduct. (Keep the existing code in the function.) -
In the
ConfirmationModalScreen, use therouteprop to say<product name> added to cart!. (Remember what parameters we're passing to the screen!)
Click here for the coded answer
return (
<CartContext.Provider value={{ cart, setCart }}>
<NavigationContainer>
<Stack.Navigator>
<Stack.Group>
<Stack.Screen name="Products" component={ProductsScreen} />
<Stack.Screen name="Details" component={DetailsScreen} />
</Stack.Group>
<Stack.Group screenOptions={{ presentation: 'modal' }}>
<Stack.Screen name="Confirmation" component={ConfirmationModalScreen} />
</Stack.Group>
</Stack.Navigator>
</NavigationContainer>
</CartContext.Provider>
);const DetailsScreen = ({ route, navigation }) => {
const { cart, setCart } = useContext(CartContext);
const product = route.params;
const onPress = () => {
setCart([...cart, product]);
navigation.navigate('Confirmation', product);
};
return (
<View style={styles.container}>
<View style={styles.content}>
<Text style={styles.title}>{product.name}</Text>
<Image style={styles.image} source={product.image} />
<Text style={styles.text}>{product.description}</Text>
<Button color="blue" onPress={onPress} title="Add to Cart" />
</View>
</View>
)
};const ConfirmationModalScreen = ({ route }) => {
return (
<View style={styles.content}>
<Text style={styles.text}>{route.params.name} added to cart!</Text>
<Button color="blue" title="Go back" />
</View>
);
};- The
navigationprop includes other handy methods for navigating our app! Let's use one such method to close the confirmation modal. In theConfirmationModalScreen, create anonPressfunction that calls thenavigation.goBackmethod. This will cause the app to move back in its history stack. Pass this function to theonPressprop of theButtoncomponent in theConfirmationModalScreen.
Click here for the coded answer
const ConfirmationModalScreen = ({ navigation, route }) => {
const onPress = () => navigation.goBack();
return (
<View style={styles.content}>
<Text style={styles.text}>{route.params.name} added to cart!</Text>
<Button onPress={onPress} color="blue" title="Go back" />
</View>
);
};-
By default, the header will display the screen's name--a little boring and uninformative for the user. Fortunately, the header can be customized at the
Navigator,Group, orScreenlevels, with each level offering more granularity. Right now, let's customize theDetailsandConfirmationscreens. Check out the docs for more info. -
The
optionsprop can either take an object or a function that receives therouteas its argument and returns an object. InApp.js, provide the confirmationStack.Screenanoptionsprop. Its value should be an object with atitlekey and a value ofThank you!. -
Pass the details
Stack.Screena function to itsoptionsprop. This function will receive an object with therouteas its argument. Return an object with the keytitleand the product's name. (Remember how params are passed around in routes!)
Click here for the coded answer
return (
<CartContext.Provider value={{ cart, setCart }}>
<NavigationContainer>
<Stack.Navigator>
<Stack.Group>
<Stack.Screen name="Products" component={ProductsScreen} />
<Stack.Screen name="Details" component={DetailsScreen} options={({ route }) => ({ title: route.params.name })} />
</Stack.Group>
<Stack.Group screenOptions={{ presentation: 'modal' }}>
<Stack.Screen name="Confirmation" component={ConfirmationModalScreen} options={{ title: 'Thank you!' }} />
</Stack.Group>
</Stack.Navigator>
</NavigationContainer>
</CartContext.Provider>
);Mild
- The header's back button can be customized as well! Check out the docs for more info. Try customizing the back button.
Medium
- Using the
headerTitleoption on a screen lets us use a custom component as our title. Create a component calledLogoTitle. Display a shopping cart icon and the textLearnShopas the title for theProductsscreen. (For the shopping cart icon, you can use Ionicons:<Ionicons name="cart-sharp" size={25} color="#192a56" />. Ionicons is imported from Expo:import Ionicons from '@expo/vector-icons/Ionicons';.) Check out the docs for more info. Remember that to display text, you must use React Native'sTextcomponent.
Spicy
- We can add a button to the right of the header! We've provided a
CartContextfor you to use. When theAdd to Cartbutton in theDetailsScreenis pressed, the product is added to the cart. Use theCartContextin a new screen calledCartand add a button with a shopping bag icon as the right header button. When this button is pressed, display theCartmodal. Check out the docs for more info on adding a right header button. You can use Ionicons for the icon:<Ionicons name="bag-handle-sharp" size={25} color="#192a56" />. Ionicons is imported from Expo:import Ionicons from '@expo/vector-icons/Ionicons';. For help with contexts, visit the React docs.