This tutorial shows you how to use the same screen for different data.
We'll assume you know how to make custom components in React Native; you may see some modern JavaScript syntax, like destructuring, that may be new to you, but I'll explain how it works. We'll also assume you already have a multi-screen app with a screen whose records are drawn from a source such as JSON, in my case an app to find lost pets.
In general, to make things easier to understand, this tutorial will emphasize simplicity of code rather than optimizing performance.
Note: this draft presents only a synopsis of this tutorial. Please watch the video for details.
We'll need some details to show in our detail screen. In its final release, an app would likely download its data from a database. In the version I'm working with today, all the data come from JSON, so we'll start by adding some content to each record that might be too long to fit in our list of records.
[
{
"id": "grumpy_cat",
"petName": "Grumpy Cat",
"petImage": "grumpy_thu.png",
"petLink": "wikipedia.org/wiki/Grumpy_Cat",
"petDescription": "An American Internet celebrity known for her permanently cranky demeanor."
},
{
"id": "nyan_cat",
"petName": "Nyan Cat",
"petImage": "example.com/nyan_cat_animated.gif",
"petLink": "wikipedia.org/wiki/Nyan_Cat",
"petDescription": "An animated cartoon cat with a Pop-Tart body that flies through space with a rainbow contrail."
},
...
]
import React from "react";
import { StyleSheet, View, Image, Text } from "react-native";
const PetDetailScreen = () => {
return (
<View style={ styles.detailScreen }>
<Text>Missing pet details</Text>
<Image source={{ uri: "wikipedia.org/wiki/Morris_the_Cat" }} />
<Text>My pet's name</Text>
<Text>Here's a long description of a very interesting pet. It's missing--can you help me find it?</Text>
</View>
);
};
export default PetDetailScreen;
Normally you wouldn't be able to see the screen or your edits until you created a way to navigate to it. So let's add it to the navigation stack as the first screen to make it easier to style or debug.
import PetDetailScreen from "./screens/PetDetailScreen";
<NavigationContainer>
<Stack.Navigator initialRouteName="PetDetail">
...
<Stack.Screen
name="PetDetail"
component={ PetDetailScreen }
options={{ title: "Pet detail" }}
/>
</Stack.Navigator>
</NavigationContainer>
const styles = StyleSheet.create({
detailScreen: {
flex: 1,
backgroundColor: "darkslategray",
alignItems: "center"
}
}
...
return (
<View style={ styles.detailScreen }>
<Text>Missing pet details</Text>
<View style={ styles.petDetailRow }>
<Image source={{ uri: "wikipedia.org/wiki/Morris_the_Cat" }} />
<Text>My pet's name</Text>
<Text>Here's a long description of a very interesting pet. It's missing--can you help me find it?</Text>
</View>
</View>
);
const styles = StyleSheet.create({
detailScreen: {
flex: 1,
alignItems: "center",
justifyContent: "flex-start",
backgroundColor: "darkslategray"
},
petDetailRow: {
flexDirection: "row",
alignSelf: "stretch"
}
)
<View style={ styles.detailScreen }>
<Text>Missing pet details</Text>
<View style={ styles.petDetailRow }>
<Image source={{ uri: "wikipedia.org/wiki/Morris_the_Cat" }} />
<View style={ styles.petDetailColumn }>
<Text>My pet's name</Text>
<Text>Here's a long description of a very interesting pet. It's missing--can you help me find it?</Text>
</View>
</View>
</View>
...
petDetailRow: {
flexDirection: "row",
alignSelf: "stretch"
},
petDetailColumn: {
width: 186,
justifyContent: "space-between"
}
In fact, nesting <Views> that alternate between flex direction row and column is a common layout pattern for detail screens, as exemplified by our layout so far.
<View style={flexDirection: "column"}>
<View style={flexDirection: "row"}>
<View style={flexDirection: "column"}>
</View>
</View>
</View>
import React from "react";
import { StyleSheet, View, Image, Text, Button, Linking } from "react-native";
const PetDetailScreen = () => {
const { petName, petImage, petDescription, petLink } = screen.route.params;
return (
<View style={ styles.detailScreen }>
<Text style={ styles.petHeading }>Missing pet details</Text>
<View style={ styles.petDetailRow }>
<Image source={{ uri: "wikipedia.org/wiki/Morris_the_Cat" }} style={ styles.image } />
<View style={ styles.petDetailColumn }>
<Text style={ styles.petName }>My pet's name</Text>
<Text style={ styles.petDescription }>Here's a long description of a very interesting pet. It's missing--can you help me find it?</Text>
</View>
</View>
</View>
);
};
export default PetDetailScreen;
const styles = StyleSheet.create({
detailScreen: {
flex: 1,
alignItems: "center",
backgroundColor: "darkslategray"
},
petHeading: {
fontSize: 27,
marginVertical: 20,
color: "paleturquoise",
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 1,
shadowRadius: 2
},
petDetailRow: {
flexDirection: "row",
alignSelf: "stretch",
marginHorizontal: 20,
padding: 10,
backgroundColor: "teal",
borderRadius: 15,
borderWidth: 1,
borderColor: "paleturquoise",
justifyContent: "space-between"
},
image: {
width: 100,
height: 100
},
petDetailColumn: {
width: 186,
justifyContent: "space-between"
},
petName: {
fontSize: 18,
fontWeight: "bold",
color: "paleturquoise"
},
petDescription: {
fontSize: 16,
marginVertical: 4,
color: "paleturquoise"
}
});
import { View, Image, Text, Button, Linking } from "react-native";
const PetDetailScreen = () => {
const { petName, petImage, petDescription, petLink } = screen.route.params;
return (
<View style={ styles.detailScreen }>
<Text style={ styles.petHeading }>Missing pet details</Text>
<View style={ styles.petDetailRow }>
<Image source={{ uri: "wikipedia.org/wiki/Morris_the_Cat" }} style={ styles.image } />
<View style={ styles.petDetailColumn }>
<Text style={ styles.petName }>My pet's name</Text>
<Text style={ styles.petDescription }>Here's a long description of a very interesting pet. It's missing--can you help me find it?</Text>
<Button
title="More..."
color="white"
onPress={
() => Linking.openURL( "wikipedia.org/wiki/Morris_the_Cat" }
}
/>
</View>
</View>
</View>
);
};
...
🆕 The technique below follows the tutorial, but see this update for a streamlined way to handle the following tasks in React Native 50 and higher.
In the screen that lists my records, I am already passing most of the properties I need to the <Card>. However, since users will navigate by tapping each card to the next (detail) screen, I also need to pass the navigation too.
const renderPets = () => {
return pets.map(pet => (
<Card
navigation={navigation}
petName={pet.petName}
petImage={pet.petImage}
petLink={pet.petLink}
petDescription={pet.petDescription}
/>
));
};
We now have the details screen in the stack, but there's still no way for a user to get to it. The most intuitive interface is for the user to be able to tap on a card in our list and go to that record.
I have a few levels to my components: my PetsScreen.js contains <Card> components, each of which in turn contains an <InfoButton> component. So I'm going to have to pass the navigation properties through this chain.
const Card = props => {
return (
...
<InfoButton
navigation={ props.navigation }
petName={ props.petName }
petImage={ props.petImage }
petLink={ props.petLink }
petDescription={ props.petDescription }
/>
Next I'll accept the <Card>'s data into each <InfoButton>. Since this is the last component in my chain, InfoButton.js is where I'll add the navigate method for jumping from one screen to another.
import React from "react";
import { Text, TouchableOpacity } from "react-native";
import { NavigationContainer } from "@react-navigation/native";
import { createStackNavigator } from "@react-navigation/stack";
const InfoButton = props => {
return (
<TouchableOpacity
onPress={
() => props.navigation.navigate(
"PetDetail"
)
}
style={ styles.button }
>
<Text style={ styles.buttonText }>i</Text>
</TouchableOpacity>
);
};
export default InfoButton;
<NavigationContainer>
<Stack.Navigator initialRouteName="Welcome">
If I test my app, this will bring me to the detail screen--yay! Unfortunately, that screen always shows my dummy text instead of details for the pet I clicked on.
To make the app show pet-specific data, I have to pass them into InfoButton.js just as I did into the Card.js, and then into the new detail screen.
import React from "react";
import { Text, TouchableOpacity } from "react-native";
import { NavigationContainer } from "@react-navigation/native";
import { createStackNavigator } from "@react-navigation/stack";
const InfoButton = props => {
return (
<TouchableOpacity
onPress={
() => props.navigation.navigate(
"PetDetail",
{
petName: props.petName,
petImage: props.petImage,
petLink: props.petLink,
petDescription: props.petDescription
}
)
}
style={ styles.button }
>
<Text style={ styles.buttonText }>i</Text>
</TouchableOpacity>
);
};
export default InfoButton;
import React from "react";
import { Text, TouchableOpacity } from "react-native";
import { NavigationContainer } from "@react-navigation/native";
import { createStackNavigator } from "@react-navigation/stack";
const InfoButton = props => {
const { petName, petImage, petLink, petDescription } = props ;
return (
<TouchableOpacity
onPress={
() => props.navigation.navigate(
"PetDetail",
{
petName,
petImage,
petLink,
petDescription
}
)
}
style={ styles.button }
>
<Text style={ styles.buttonText }>i</Text>
</TouchableOpacity>
);
};
export default InfoButton;
Finally we're ready to specify which pet to show in our detail screen. Regardless of which syntax we choose in InfoButton.js, we can write the parameters set there into PetDetailScreen.js.
const PetDetailScreen = ( screen, params ) => {
const petName = screen.route.params.petName ;
const petImage = screen.route.params.petImage ;
const petDescription = screen.route.params.petDescription ;
const petLink = screen.route.params.petLink ;
const { petName, petImage, petDescription, petLink } = screen.route.params;
import React from "react";
import { StyleSheet, View, Image, Text, Button, Linking } from "react-native";
const PetDetailScreen = ( screen, params ) => {
const { petName, petImage, petDescription, petLink } = screen.route.params;
return (
<View style={ styles.detailScreen }>
<Text style={ styles.petHeading }>Missing pet details</Text>
<View style={ styles.petDetailRow }>
<Image source={{ uri: petImage }} style={ styles.image } />
<View style={ styles.petDetailColumn }>
<Text style={ styles.petName }>{ petName }</Text>
<Text style={ styles.petDescription }>{ petDescription }</Text>
<Button
title="More..."
color="white"
onPress={
() => Linking.openURL( petLink }
}
/>
</View>
</View>
</View>
);
};
export default PetDetailScreen;
const styles = StyleSheet.create({
detailScreen: {
flex: 1,
alignItems: "center",
backgroundColor: "darkslategray"
},
petHeading: {
fontSize: 27,
marginVertical: 20,
color: "paleturquoise",
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 1,
shadowRadius: 2
},
petDetailRow: {
flexDirection: "row",
alignSelf: "stretch",
marginHorizontal: 20,
padding: 10,
backgroundColor: "teal",
borderRadius: 15,
borderWidth: 1,
borderColor: "paleturquoise",
justifyContent: "space-between"
},
image: {
width: 100,
height: 100
},
petDetailColumn: {
width: 186,
justifyContent: "space-between"
},
petName: {
fontSize: 18,
fontWeight: "bold",
color: "paleturquoise"
},
petDescription: {
fontSize: 16,
marginVertical: 4,
color: "paleturquoise"
}
});
Test it and we can see that different cards yield different detail data--all rendered from the same screen, thanks to passing properties with React Navigation.