Skip to main content

React components

Now we'd like to display multiple of these shopping list items without having to copy paste the code.

For this, we should pull the ShoppingListItem into a separate component.

Tip

Add the no unused styles eslint plugin to easily detect when styles defined in a StyleSheet are not used in the file. It makes refactorings such as this much easier.

Instructions for configuring react-native/no-unused-styles
# if using yarn
yarn add -D eslint-plugin-react-native

# if using other package managers
npx expo install -- --save-dev eslint-plugin-react-native

and add the no unused styles rule to your .eslintrc.js

 module.exports = {
extends: ["expo", "prettier"],
- plugins: ["prettier"],
+ plugins: ["prettier", "react-native"],
rules: {
"prettier/prettier": "error",
+ "react-native/no-unused-styles": "error",
},
};

Create a new component for components/ShoppingListItem.tsx and use it in App.tsx. Pass in the item name as a prop.

As part of the refactor, use template strings to incorporate the item name in the deletion confirmation message.

New file: components/ShoppingListItem.tsx
import { TouchableOpacity, View, Alert, StyleSheet, Text } from "react-native";
import { theme } from "../theme";

type Props = {
name: string;
};

export function ShoppingListItem({ name }: Props) {
const handleDelete = () => {
Alert.alert(
`Are you sure you want to delete ${name}?`,
"It will be gone for good",
[
{
text: "Yes",
onPress: () => console.log("Ok, deleting."),
style: "destructive",
},
{ text: "Cancel", style: "cancel" },
],
);
};

return (
<View style={styles.itemContainer}>
<Text style={styles.itemText}>{name}</Text>
<TouchableOpacity
onPress={handleDelete}
style={styles.button}
activeOpacity={0.8}
>
<Text style={styles.buttonText}>Delete</Text>
</TouchableOpacity>
</View>
);
}

const styles = StyleSheet.create({
itemContainer: {
paddingVertical: 16,
paddingHorizontal: 8,
borderBottomColor: theme.colorCerulean,
borderBottomWidth: 1,
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
},
itemText: {
fontSize: 18,
fontWeight: "200",
},
button: {
backgroundColor: theme.colorBlack,
padding: 8,
borderRadius: 6,
},
buttonText: {
color: "#fff",
fontWeight: "bold",
textTransform: "uppercase",
letterSpacing: 1,
},
});

Update: App.tsx
@@ -1,33 +1,13 @@
-import { StyleSheet, Text, TouchableOpacity, View, Alert } from "react-native";
+import { StyleSheet, View } from "react-native";
import { theme } from "./theme";
+import { ShoppingListItem } from "./components/ShoppingListItem";

export default function App() {
- const handleDelete = () => {
- Alert.alert(
- "Are you sure you want to delete this?",
- "It will be gone for good",
- [
- {
- text: "Yes",
- onPress: () => console.log("Ok, deleting."),
- style: "destructive",
- },
- { text: "Cancel", style: "cancel" },
- ],
- );
- };
return (
<View style={styles.container}>
- <View style={styles.itemContainer}>
- <Text style={styles.itemText}>Coffee</Text>
- <TouchableOpacity
- onPress={handleDelete}
- style={styles.button}
- activeOpacity={0.8}
- >
- <Text style={styles.buttonText}>Delete</Text>
- </TouchableOpacity>
- </View>
+ <ShoppingListItem name="Coffee" />
+ <ShoppingListItem name="Tea" />
+ <ShoppingListItem name="Milk" />
</View>
);
}
@@ -38,28 +18,4 @@ const styles = StyleSheet.create({
backgroundColor: theme.colorWhite,
justifyContent: "center",
},
- itemContainer: {
- paddingVertical: 16,
- paddingHorizontal: 8,
- borderBottomColor: theme.colorCerulean,
- borderBottomWidth: 1,
- flexDirection: "row",
- alignItems: "center",
- justifyContent: "space-between",
- },
- itemText: {
- fontSize: 18,
- fontWeight: "200",
- },
- button: {
- backgroundColor: theme.colorBlack,
- padding: 8,
- borderRadius: 6,
- },
- buttonText: {
- color: "#fff",
- fontWeight: "bold",
- textTransform: "uppercase",
- letterSpacing: 1,
- },
});

Checkpoint

AndroidiOS