Delete and complete
We've set up our UI to delete our shopping list items or mark them as done, but we haven't actually hooked this stuff up. Let's do it now.
Delete an item
In the ShoppingListItem
, add an onDelete
prop that's a function to be called instead of the console.log
when the user confirms they want to delete the item.
Update: components/ShoppingListItem.tsx
@@ -6,9 +6,10 @@ import { theme } from "../theme";
type Props = {
name: string;
isCompleted?: boolean;
+ onDelete: () => void;
};
-export function ShoppingListItem({ name, isCompleted }: Props) {
+export function ShoppingListItem({ name, isCompleted, onDelete }: Props) {
const handleDelete = () => {
Alert.alert(
`Are you sure you want to delete ${name}?`,
@@ -16,7 +17,7 @@ export function ShoppingListItem({ name, isCompleted }: Props) {
[
{
text: "Yes",
- onPress: () => console.log("Ok, deleting."),
+ onPress: () => onDelete(),
style: "destructive",
},
{ text: "Cancel", style: "cancel" },
In the index route, add a handleDelete
function that removes the selected item from the shopping list.
Update: app/index.tsx
@@ -23,6 +23,11 @@ export default function App() {
}
};
+ const handleDelete = (id: string) => {
+ const newShoppingList = shoppingList.filter((item) => item.id !== id);
+ setShoppingList(newShoppingList);
+ };
+
return (
<FlatList
ListHeaderComponent={
@@ -44,7 +49,12 @@ export default function App() {
style={styles.container}
contentContainerStyle={styles.contentContainer}
stickyHeaderIndices={[0]}
- renderItem={({ item }) => <ShoppingListItem name={item.name} />}
+ renderItem={({ item }) => (
+ <ShoppingListItem
+ name={item.name}
+ onDelete={() => handleDelete(item.id)}
+ />
+ )}
></FlatList>
);
}
Complete (or un-complete) an item
Let's add a completedAt
prop to our shopping list items and toggle it from the current timestamp to undefined
whenever the shopping list item is tapped.
Let's replace the containing View
in ShoppingList
with a Pressable
, add a new onToggleComplete
prop and call it when the item is pressed.
Update: components/ShoppingListItem.tsx
@@ -1,4 +1,11 @@
-import { TouchableOpacity, View, Alert, StyleSheet, Text } from "react-native";
+import {
+ TouchableOpacity,
+ View,
+ Alert,
+ StyleSheet,
+ Text,
+ Pressable,
+} from "react-native";
import AntDesign from "@expo/vector-icons/AntDesign";
import Entypo from "@expo/vector-icons/Entypo";
import { theme } from "../theme";
@@ -7,9 +14,15 @@ type Props = {
name: string;
isCompleted?: boolean;
onDelete: () => void;
+ onToggleComplete: () => void;
};
-export function ShoppingListItem({ name, isCompleted, onDelete }: Props) {
+export function ShoppingListItem({
+ name,
+ isCompleted,
+ onDelete,
+ onToggleComplete,
+}: Props) {
const handleDelete = () => {
Alert.alert(
`Are you sure you want to delete ${name}?`,
@@ -26,11 +39,12 @@ export function ShoppingListItem({ name, isCompleted, onDelete }: Props) {
};
return (
- <View
+ <Pressable
style={[
styles.itemContainer,
isCompleted ? styles.completedContainer : undefined,
]}
+ onPress={onToggleComplete}
>
<View style={styles.row}>
<Entypo
@@ -54,7 +68,7 @@ export function ShoppingListItem({ name, isCompleted, onDelete }: Props) {
color={isCompleted ? theme.colorGrey : theme.colorRed}
/>
</TouchableOpacity>
- </View>
+ </Pressable>
);
}
Now add a onToggleComplete
function that sets or unsets the completedAt
prop on the shopping list item.
We'll need to also pass in the isCompleted
prop as true when the date is set, and false otherwise.
Update: app/index.tsx
@@ -6,6 +6,7 @@ import { useState } from "react";
type ShoppingListItemType = {
id: string;
name: string;
+ completedAtTimestamp?: number;
};
export default function App() {
@@ -28,6 +29,22 @@ export default function App() {
setShoppingList(newShoppingList);
};
+ const handleToggleComplete = (id: string) => {
+ const newShoppingList = shoppingList.map((item) => {
+ if (item.id === id) {
+ return {
+ ...item,
+ completedAtTimestamp: item.completedAtTimestamp
+ ? undefined
+ : Date.now(),
+ };
+ } else {
+ return item;
+ }
+ });
+ setShoppingList(newShoppingList);
+ };
+
return (
<FlatList
ListHeaderComponent={
@@ -53,6 +70,8 @@ export default function App() {
<ShoppingListItem
name={item.name}
onDelete={() => handleDelete(item.id)}
+ onToggleComplete={() => handleToggleComplete(item.id)}
+ isCompleted={Boolean(item.completedAtTimestamp)}
/>
)}
></FlatList>
Order by last updated
Finally, let's do some ordering. To keep things simple, we'll write a little utility function for this and order the data just before passing it into the FlatList
. The requirements are:
- display the incomplete items first
- when a new item is added, it gets put at the top of the incomplete pile
- when it is marked as done, it goes to the top of the complete pile
- when a previously completed item is marked incomplete, it goes back to the top of the incomplete pile
Since last interactions are important here, we need an extra prop and that is the lastUpdatedTimestamp
.
Let's add that to the shopping list type and make sure we set it whenever we add or edit an item:
Update: app/index.tsx
@@ -7,6 +7,7 @@ type ShoppingListItemType = {
id: string;
name: string;
completedAtTimestamp?: number;
+ lastUpdatedTimestamp: number;
};
export default function App() {
@@ -16,7 +17,11 @@ export default function App() {
const handleSubmit = () => {
if (value) {
const newShoppingList = [
- { id: new Date().toISOString(), name: value },
+ {
+ id: new Date().toISOString(),
+ name: value,
+ lastUpdatedTimestamp: Date.now(),
+ },
...shoppingList,
];
setShoppingList(newShoppingList);
@@ -37,6 +42,7 @@ export default function App() {
completedAtTimestamp: item.completedAtTimestamp
? undefined
: Date.now(),
+ lastUpdatedTimestamp: Date.now(),
};
} else {
return item;
Now we have everything we need to order our items using the JavaScript sort function.
With the sort function you pass in one function, (a, b) => number
and you return a positive number if a should be before b, and a negative number if b is before a. That's why using timestamps here is handy since you can subtract them form each other to get the right ordering.
Here's the ordering function:
Ordering function
function orderShoppingList(shoppingList: ShoppingListItemType[]) {
return shoppingList.sort((item1, item2) => {
if (item1.completedAtTimestamp && item2.completedAtTimestamp) {
return item2.completedAtTimestamp - item1.completedAtTimestamp;
}
if (item1.completedAtTimestamp && !item2.completedAtTimestamp) {
return 1;
}
if (!item1.completedAtTimestamp && item2.completedAtTimestamp) {
return -1;
}
if (!item1.completedAtTimestamp && !item2.completedAtTimestamp) {
return item2.lastUpdatedTimestamp - item1.lastUpdatedTimestamp;
}
return 0;
});
}
And we'll sort the array just before passing it to the FlatList
Update: app/index.tsx
@@ -68,7 +68,7 @@ export default function App() {
<Text>Your shopping list is empty</Text>
</View>
}
- data={shoppingList}
+ data={orderShoppingList(shoppingList)}
style={styles.container}
contentContainerStyle={styles.contentContainer}
stickyHeaderIndices={[0]}
@@ -84,6 +84,28 @@ export default function App() {
);
}
+function orderShoppingList(shoppingList: ShoppingListItemType[]) {
+ return shoppingList.sort((item1, item2) => {
+ if (item1.completedAtTimestamp && item2.completedAtTimestamp) {
+ return item2.completedAtTimestamp - item1.completedAtTimestamp;
+ }
+
+ if (item1.completedAtTimestamp && !item2.completedAtTimestamp) {
+ return 1;
+ }
+
+ if (!item1.completedAtTimestamp && item2.completedAtTimestamp) {
+ return -1;
+ }
+
+ if (!item1.completedAtTimestamp && !item2.completedAtTimestamp) {
+ return item2.lastUpdatedTimestamp - item1.lastUpdatedTimestamp;
+ }
+
+ return 0;
+ });
+}
+
const styles = StyleSheet.create({
container: {
backgroundColor: theme.colorWhite,