- 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 theNavigationContainer
from@react-navigation/native
. This is a named export. -
It's necessary to wrap your app in the
NavigationContainer
component in order for React Navigation to work. InApp.js
, add theNavigationContainer
within theCartContext.Provider
component.
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
createNativeStackNavigator
function from@react-navigation/native-stack
. Outside of theApp
component, 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
createBottomTabNavigator
function from the previous React Navigation lab,createNativeStackNavigator
returns an object withNavigator
andScreen
components. TheNavigator
component creates the stack navigator; it must be the parent of theScreen
components. TheScreen
component creates the individual screens in your app. You can have as manyScreen
components as needed. (Later, we'll use a newGroup
component to group our screens together.) -
Add the
Stack.Navigator
as a child of theNavigationContainer
. Then, add twoStack.Screen
components as children of theStack.Navigator
. Add aStack.Screen
with aname
prop ofProducts
and pass theProductsScreen
component as itscomponent
prop. Do the same for theDetailsScreen
and 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
navigation
prop, we don't need any extra props! Just pass the screen components to thecomponent
prop--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.navigate
and providing it with the name of a screen, you're be able to navigate to a new screen. Thisnavigation
prop is provided to every screen component. To use thenavigation
object in a component that isn't a screen, use theuseNavigation
hook. -
We want to navigate to the details when we click the
ProductCard
, so we'll need to give thePressable
component inProductCard
anonPress
handler. InProductCard
, write a function that callsnavigation.navigate
. You'll need to import{ useNavigation }
from@react-navigation/native
. Thenavigation.navigate
method 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 objectproduct
to thenavigation.navigate
function. (This will be the sameproduct
passed to eachProductCard
.) Pass the function you write to theonPress
prop of thePressable
component. -
In the app, click on a product card. You can successfully navigate to the
Details
page! 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
route
prop contains information about the current route. Like thenavigation
prop,route
is passed as a prop to screen components. For other components, you can use theuseRoute
hook to access theroute
object. Check out the React Navigation docs for more info. -
In the
DetailsScreen
, destructureroute
from the props passed to the component. Assign theproduct
variable 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 Cart
button in theDetailsScreen
is 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 theGroup
component included inStack
to separate out our screens.Group
gives us a little more granular control. Wrap theStack.Screen
components forProducts
andDetails
in aStack.Group
component. -
Create a separate
Stack.Group
as a child of theStack.Navigator
.Stack.Group
can take ascreenOptions
prop, allowing for a wide range of customization. For this particularStack.Group
, passscreenOptions
an object with a key ofpresentation
and 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.Screen
namedConfirmation
and give itConfirmationModalScreen
as itscomponent
prop. -
In the
DetailsScreen
component, destructure thenavigation
prop. In the existingonPress
function, navigate to theConfirmation
screen usingnavigation.navigate
and pass it theproduct
. (Keep the existing code in the function.) -
In the
ConfirmationModalScreen
, use theroute
prop 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
navigation
prop includes other handy methods for navigating our app! Let's use one such method to close the confirmation modal. In theConfirmationModalScreen
, create anonPress
function that calls thenavigation.goBack
method. This will cause the app to move back in its history stack. Pass this function to theonPress
prop of theButton
component 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
, orScreen
levels, with each level offering more granularity. Right now, let's customize theDetails
andConfirmation
screens. Check out the docs for more info. -
The
options
prop can either take an object or a function that receives theroute
as its argument and returns an object. InApp.js
, provide the confirmationStack.Screen
anoptions
prop. Its value should be an object with atitle
key and a value ofThank you!
. -
Pass the details
Stack.Screen
a function to itsoptions
prop. This function will receive an object with theroute
as its argument. Return an object with the keytitle
and 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
headerTitle
option on a screen lets us use a custom component as our title. Create a component calledLogoTitle
. Display a shopping cart icon and the textLearnShop
as the title for theProducts
screen. (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'sText
component.
Spicy
- We can add a button to the right of the header! We've provided a
CartContext
for you to use. When theAdd to Cart
button in theDetailsScreen
is pressed, the product is added to the cart. Use theCartContext
in a new screen calledCart
and add a button with a shopping bag icon as the right header button. When this button is pressed, display theCart
modal. 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.