Editing Tasks
In this tutorial, weβll add the ability for users to edit their tasks by tapping on them. This will open a dialog where they can modify the taskβs title and category.
Step 1: Install the Portal Component
Section titled βStep 1: Install the Portal ComponentβFirst, we need to install the Portal component from React Native Primitives:
npx expo install @rn-primitives/portal
Step 2: Update the Root Layout
Section titled βStep 2: Update the Root LayoutβWe need to add a Portal Host to our root layout to support dialogs:
import { PortalHost } from "@rn-primitives/portal";
// Inside your return statement, wrap the Tab.Navigator in a fragment// and add the PortalHost component after itreturn ( <> <Tab.Navigator> {/* existing Tab.Navigator code */} </Tab.Navigator> <PortalHost /> </>);
Step 3: Install Dialog Components
Section titled βStep 3: Install Dialog ComponentsβInstall the dialog components from React Native Reusables:
npx @react-native-reusables/cli@latest add dialognpx @react-native-reusables/cli@latest add inputnpx @react-native-reusables/cli@latest add button
Step 4: Update the Task Component
Section titled βStep 4: Update the Task ComponentβNow weβll modify our Task component to include the dialog functionality:
import { TouchableOpacity, View } from "react-native";import { Dialog, DialogClose, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogTrigger, DialogDescription,} from "~/components/ui/dialog";import { Button } from "~/components/ui/button";import { Input } from "~/components/ui/input";
interface TaskProps { title: string; category: string; isChecked: boolean; onEdit?: (newTitle: string, newCategory: string) => void;}
Step 5: Implement the Dialog UI
Section titled βStep 5: Implement the Dialog UIβReplace the Task component implementation with this updated version:
function Task({ title, category, isChecked, onEdit }: TaskProps) { const [checked, setChecked] = React.useState(isChecked);
return ( <Dialog> <DialogTrigger asChild> <TouchableOpacity className="flex flex-row w-full" delayLongPress={500}> <View className="px-8 py-5 flex w-24 h-full"> <Checkbox className="border-foreground checked:bg-foreground" checked={checked} onCheckedChange={setChecked} /> </View> <View className="py-4 flex gap-1 flex-1 h-full border-b border-foreground-transparent"> <Text className="text-foreground text-xl">{title}</Text> <Text className="text-foreground-transparent text-xl"> {category} </Text> </View> </TouchableOpacity> </DialogTrigger>
<DialogContent> <DialogHeader> <DialogTitle>Edit Task</DialogTitle> <DialogDescription> Make changes to your task details here. </DialogDescription> </DialogHeader>
<View className="gap-4"> <Input defaultValue={title} placeholder="Task title" /> <Input defaultValue={category} placeholder="Category" /> </View>
<DialogFooter> <DialogClose asChild> <Button variant="outline"> <Text>Cancel</Text> </Button> </DialogClose> <DialogClose asChild> <Button> <Text>Save changes</Text> </Button> </DialogClose> </DialogFooter> </DialogContent> </Dialog> );}
Whatβs Happening Here?
Section titled βWhatβs Happening Here?β- Weβve wrapped our task in a
Dialog
component - The
DialogTrigger
opens the dialog when the user taps on a task - Inside
DialogContent
, weβve created:- A header with title and description
- Input fields pre-filled with the current task details
- Footer buttons to save or cancel changes
Testing Your Changes
Section titled βTesting Your ChangesβAfter implementing these changes:
- Tap on any task in your app
- A dialog will appear with the current title and category
- You can modify these values (though we havenβt implemented saving yet)
- Click βCancelβ to close without saving or βSave changesβ to close the dialog
Step 6: Create a separate Task Component file
Section titled βStep 6: Create a separate Task Component fileβFirst, letβs create a new file for our Task component:
touch app/components/Task.tsx
Then, move the Task component code from app/index.tsx
to app/components/Task.tsx
.
import * as React from "react";import { TouchableOpacity, View } from "react-native";import { Checkbox } from "~/components/ui/checkbox";import { Text } from "~/components/ui/text";import TaskDialog from "./TaskDialogue";
export interface Task { id: number; title: string; category: string; isChecked: boolean;}
export interface TaskProps { task: Task;}
export default function Task({ task: propTask }: TaskProps) { const [task, setTask] = React.useState(propTask); const [showDialog, setShowDialog] = React.useState(false); const { title, category, isChecked } = task;
const handleSetChecked = () => { const nextChecked = !task.isChecked; setTask({ ...task, isChecked: nextChecked }); };
return ( <> <TouchableOpacity className="flex flex-row w-full bg-gray-800" delayLongPress={500} onLongPress={() => setShowDialog(true)} > <View className="px-8 pt-8 w-24 h-full"> <Checkbox className="border-foreground checked:bg-foreground" checked={isChecked} onCheckedChange={handleSetChecked} /> </View> <View className="py-4 flex gap-1 flex-1 h-full border-b border-foreground-transparent"> <Text className="text-foreground text-xl">{title}</Text> <Text className="text-foreground-transparent text-xl"> {category} </Text> </View> </TouchableOpacity>
<TaskDialog task={task} setTask={setTask} showDialog={showDialog} setShowDialog={setShowDialog} /> </> );}
##Β Step 7: Create the TaskDialog Component
Create a new file for the TaskDialog component:
touch app/components/TaskDialogue.tsx
Then, move the TaskDialog component code from app/index.tsx
to app/components/TaskDialogue.tsx
.
import React from "react";import { View } from "react-native";import { Task } from "./Task";import { Button } from "./ui/button";import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle,} from "./ui/dialog";import { Input } from "./ui/input";import { Text } from "./ui/text";
interface TaskDialogProps { task: Task; setTask: (task: Task) => void; setShowDialog: (showDialog: boolean) => void; showDialog: boolean;}
export default function TaskDialog({ task, setTask, setShowDialog, showDialog,}: TaskDialogProps) { const [editedTitle, setEditedTitle] = React.useState(task.title); const [editedCategory, setEditedCategory] = React.useState(task.category);
const { title, category } = task;
const handleUpdateTitle = (title: string) => { setEditedTitle(title); };
const handleUpdateCategory = (category: string) => { setEditedCategory(category); };
const handleSave = () => { const nextTask = { ...task, title: editedTitle, category: editedCategory, };
setTask(nextTask); setShowDialog(false); };
return ( <Dialog open={showDialog} onOpenChange={setShowDialog}> <DialogContent> <DialogHeader> <DialogTitle>Edit Task</DialogTitle> <DialogDescription> Make changes to your task details here. </DialogDescription> </DialogHeader>
<View className="gap-4"> <Input defaultValue={title} placeholder="Task title" onChangeText={handleUpdateTitle} /> <Input defaultValue={category} placeholder="Category" onChangeText={handleUpdateCategory} /> </View>
<DialogFooter> <DialogClose asChild> <Button variant="outline"> <Text>Cancel</Text> </Button> </DialogClose> <DialogClose asChild> <Button onPress={handleSave}> <Text>Save changes</Text> </Button> </DialogClose> </DialogFooter> </DialogContent> </Dialog> );}
Step 8: Update the HomeScreen Component
Section titled βStep 8: Update the HomeScreen ComponentβFinally, letβs update our HomeScreen component to use the new Task component:
import * as React from "react";import { View } from "react-native";import Task from "~/components/Task";
interface TaskProps { title: string; category: string; isChecked: boolean; onEdit?: (newTitle: string, newCategory: string) => void;}function Task({ title, category, isChecked, onEdit }: TaskProps) { const [checked, setChecked] = React.useState(isChecked);
return ( <Dialog> <DialogTrigger asChild> <TouchableOpacity className="flex flex-row w-full" delayLongPress={500}> <View className="px-8 py-5 flex w-24 h-full"> <Checkbox className="border-foreground checked:bg-foreground" checked={checked} onCheckedChange={setChecked} /> </View> <View className="py-4 flex gap-1 flex-1 h-full border-b border-foreground-transparent"> <Text className="text-foreground text-xl">{title}</Text> <Text className="text-foreground-transparent text-xl"> {category} </Text> </View> </TouchableOpacity> </DialogTrigger>
<DialogContent> <DialogHeader> <DialogTitle>Edit Task</DialogTitle> <DialogDescription> Make changes to your task details here. </DialogDescription> </DialogHeader>
<View className="gap-4"> <Input defaultValue={title} placeholder="Task title" /> <Input defaultValue={category} placeholder="Category" /> </View>
<DialogFooter> <DialogClose asChild> <Button variant="outline"> <Text>Cancel</Text> </Button> </DialogClose> <DialogClose asChild> <Button> <Text>Save changes</Text> </Button> </DialogClose> </DialogFooter> </DialogContent> </Dialog> );}
export default function HomeScreen() { const tasks = [ { id: 1, title: "Task 1", category: "Category 1", isChecked: false }, { id: 2, title: "Task 2", category: "Category 2", isChecked: false }, { id: 3, title: "Task 3", category: "Category 3", isChecked: false }, { id: 4, title: "Task 4", category: "Category 2", isChecked: true }, ];
return ( <View className="flex-1 justify-center items-center gap-5 p-6 bg-background"> {tasks.map((task) => ( <Task key={task.id} task={task} /> ))} </View> );}
Whatβs Happening Here?
Section titled βWhatβs Happening Here?β- Code Organization: Weβve moved the Task component to its own file and created a separate TaskDialog component for better code organization.
- Task Component:
- Now accepts a complete
task
object instead of individual props - Manages its own state with
useState
- Opens the dialog on long press with
onLongPress
- Handles checkbox state changes with
handleSetChecked
- Now accepts a complete
- TaskDialog Component:
- Manages the edited title and category with separate state variables
- Provides handlers for updating the title and category
- Implements a
handleSave
function that updates the task with the edited values - Closes the dialog after saving
- HomeScreen Component:
- Simplified to just render the Task components with the task data
Testing Your Changes
Section titled βTesting Your ChangesβAfter implementing these changes:
- Long-press on any task in your app
- A dialog will appear with the current title and category
- You can modify these values
- Click βCancelβ to close without saving or βSave changesβ to close the dialog
- The task will update with the new values
This implementation provides a complete task editing experience with proper state management and component organization.
Next Steps
Section titled βNext StepsβIn the next tutorial, we will look at how to add a new task to the list.