2. Expo Router
2. Expo Router
In deze les bespreken we hoe we routing en verschillende pagina's kunnen toevoegen aan een Expo applicatie. Hiervoor gebruiken we opnieuw een To-Do applicaties.
Startbestanden
We vertrekken voor deze les van onderstaande startbestanden. De structuur van dit project verschilt licht met die van het project uit de vorige les. Dit komt om het project nu gegenereerd is met de Navigation (TypeScript) template in de plaats van de Blank (TypeScript) template. Daarna zijn de demo files uit de template verwijdert.
Startbestanden
Expo router
Expo Router is, net zoals de routers in de andere twee grote React frameworks (Next.js en Remix) een file-based router. Wat wil zeggen dat de Routes en Route componenten die we in frontend frameworks gebruikt hebben niet meer nodig zijn.
De routing structuur wordt opgebouwd op basis van de bestanden in de app directory, dezelfde naam die ook wordt gebruikt in Next en Remix. Elk bestand wordt automatisch omgezet in een route, hieronder een heel eenvoudig voorbeeld.
app
|---index.tsx // Komt overeen met de route /
|---about.tsx // Komt overeen met de route /about
|---dashboard
| |---index.tsx // Komt overeen met de route /dashboard
| |---sales.tsx // Komt overeen met de route /dashboard/salesIndex route
Elke Expo Router applicatie heeft verplicht een index route, dit de route die automatisch getoond wordt als de applicatie opstart.
Voorlopig renderen we enkel de string "Welkom" zodat we de applicatie succesvol kunnen compileren en uitvoeren.
import {type FunctionComponent} from 'react'
import {View} from 'react-native'
import StyledTitle from '@/components/styledTitle'
const Index: FunctionComponent = () => {
return (
<View>
<StyledTitle>Welkom</StyledTitle>
</View>
)
}
export default Index
Layout route
De app router ondersteund layout routes, alhoewel deze vergelijkbaar zijn met de layout-routes in Next (zie backend frameworks les 1) zijn er toch enkele belangrijke verschillen. Layout routes in Expo Router beschrijven niet alleen de gemeenschappelijke layout van verschillende pagina's, maar definiëren ook routes, pagina titels en de manier waarop er genavigeerd moet worden.
Begrip: Layout routes
Binnen de Expo Router kunnen layout routes aangemaakt worden.
Dit zijn bestanden die:
- Verplicht de naam _layout.tsx krijgen
- Code definiëren die rond alle kind routes geplaatst worden
- Navigator componenten bevatten voor een stack, tabs, drawer, ... layout
- De Slot component gebruiken in de plaats van children om de kind routes te renderen
De startbestanden bevatten al een ThemeProvider die het thema van de applicatie instelt, een statusbalk toevoegt en een SafeAreaView rendert. We kunnen deze component dus gebruiken in combinatie met een layout route om de visuele problemen in bovenstaand screenshot op te lossen.
import {Slot} from 'expo-router'
import {FunctionComponent} from 'react'
import ThemeProvider from '@/context/themeProvider'
const RootLayout: FunctionComponent = () => {
return (
<ThemeProvider>
<Slot />
</ThemeProvider>
)
}
export default RootLayoutNa deze aanpassing ziet de applicatie er als volgt uit.

Links
Naast de / route, hebben we ook een /labels en /tasks route nodig. We maken deze routes alvast aan en plaatsen hier voorlopig een tekst in die de naam van de pagina weergeeft.
Verder voegen we op de indexpagina een link naar de twee nieuwe pagina's toe en op de twee nieuwe pagina's een link terug naar de indexpagina. Hiervoor gebruiken we de Link component uit Expo Router. Deze component accepteert zowel absolute als relatieve links.
import {type FunctionComponent} from 'react'
import {View} from 'react-native'
import StyledTitle from '@/components/styledTitle'
import {Link} from 'expo-router'
const Index: FunctionComponent = () => {
return (
<View>
<StyledTitle>Welkom</StyledTitle>
<StyledTitle>
<Link href="/labels">Go to labels</Link>
</StyledTitle>
<StyledTitle>
<Link href="/tasks">Go to tasks </Link>
</StyledTitle>
</View>
)
}
export default Indeximport {FunctionComponent} from 'react'
import StyledTitle from '@/components/styledTitle'
import {Link} from 'expo-router'
const TasksPage: FunctionComponent = () => {
return (
<>
<StyledTitle>Tasks</StyledTitle>
<StyledTitle>
<Link href="..">Back to home</Link>
</StyledTitle>
</>
)
}
export default TasksPageimport {FunctionComponent} from 'react'
import StyledTitle from '@/components/styledTitle'
import {Link} from 'expo-router'
const LabelsPage: FunctionComponent = () => {
return (
<>
<StyledTitle>Labels</StyledTitle>
<StyledTitle>
<Link href="..">Back to home</Link>
</StyledTitle>
</>
)
}
export default LabelsPageOnderstaande video illustreert de navigatie tussen verschillende schermen.
Navigators
We kunnen nu navigeren tussen de verschillende pagina's, maar de applicatie ziet er nog niet echt uit als een mobiele applicatie. Expo Router voorziet verschillende navigators die integreren met het UI van het operating system. Een navigator is een component die de navigatie van de applicatie afhandelt, dit kan via een stack, tabs, modal of drawer.
Hieronder bespreken we drie van de vier navigators. De drawer navigator wordt niet besproken omdat deze navigatiestructuur enkel gebruikt wordt in Android applicaties en niet op iOS. Drawers zijn lastig om te implementeren omdat deze open geswipet kunnen worden, de gestures die hiervoor nodig zijn kunnen in conflict gaan met andere gestures in de applicatie of het operating system.
We raden een drawer enkel aan als je meer dan 5 hoofdmenu-items hebt, anders is een tabs navigator een betere keuze. Dit is normaliter niet het geval voor de app die je voor dit vak bouwt, dus wordt de drawer navigator niet besproken. De geïnteresseerde lezer kan in de documentatie informatie vinden over het gebruik van een drawer navigator.
Tabs navigator
De eerste navigator die we bespreken is de Tabs navigator. Expo Router kan, zonder extra dependencies, gebruikt worden om tabs toe te voegen.
Zodra we een navigator willen gebruiken, mogen we geen gebruik meer maken van de Slot component in de layout-route. In plaats daarvan moeten we de navigator oproepen en de verschillende routes als kind doorgeven via de Navigator.Screen component. Hier wordt Navigator natuurlijk vervangen met de naam van de navigator die we willen gebruiken.
De Tabs.Screen component (en elke andere Navigator.Screen component) heeft één verplichte parameter, het pad van de route die doorgegeven moet worden via de name property. Verder kunnen we via options property de titel van de pagina instellen en een icoon toevoegen aan de tabbalk. De tabBarIcon property krijgt een functie als argument die, onder andere, de kleur en grootte van het icoon als argument neemt.
import {FunctionComponent} from 'react'
import ThemeProvider from '@/context/themeProvider'
import {Tabs} from 'expo-router'
import {ListTodoIcon, TagIcon} from 'lucide-react-native'
const RootLayout: FunctionComponent = () => {
return (
<ThemeProvider>
<Tabs>
<Tabs.Screen
name="tasks/index"
options={ {
title: 'Tasks',
tabBarIcon: ({color, size}) => <ListTodoIcon size={size} color={color} />,
}}
/>
<Tabs.Screen
name="labels/index"
options={ {
title: 'Labels',
tabBarIcon: ({color, size}) => <TagIcon size={size} color={color} />,
}}
/>
</Tabs>
</ThemeProvider>
)
}
export default RootLayoutNu dat we een navigator toegevoegd hebben, ziet er app er als volgt uit:

Het is duidelijk dat er nog verschillende problemen zijn. Om te beginnen staat er nog een extra link in de tabs navigator, dit komt omdat React navigation steeds alle files in de app directory uitleest. Als je deze niet expliciet toevoegt aan een navigator, worden deze automatisch toegevoegd aan de dichtstbijzijnde navigator.
We hebben de index route natuurlijk niet nodig, als de app start moet de gebruiker onmiddellijk op het tasks tabblad terechtkomen. We kunnen deze route echter niet gewoon verwijderen omdat Expo Router een index route verplicht. Om dit probleem op te lossen moeten we de / route ook toevoegen aan de tabs navigator, vervolgens kunnen we het redirect attribuut toevoegen. Als dit booleaans attribuut op true staat, wordt de gebruiker automatisch doorgestuurd naar het eerstvolgende kind van de navigator. Aangezien de route niet bezocht kan worden, wordt deze ook niet toegevoegd in de navigatiebalk.
const RootLayout: FunctionComponent = () => {
return (
<ThemeProvider>
<Tabs>
<Tabs.Screen name="index" redirect />
<Tabs.Screen
name="tasks/index"
options={ {
title: 'Tasks',
tabBarIcon: ({color, size}) => <ListTodoIcon size={size} color={color} />,
}}
/>
<Tabs.Screen
name="labels/index"
options={ {
title: 'Labels',
tabBarIcon: ({color, size}) => <TagIcon size={size} color={color} />,
}}
/>
</Tabs>
</ThemeProvider>
)
}Na deze aanpassing ziet de app er als volgt uit:

Alhoewel navigatie werkt, is het design absoluut niet in orde.
Styling navigators
Het thema uit de ThemeProvider component wordt niet (volledig) toegepast op de navigator componenten. Dit probleem kan opgelost worden door theming informatie mee te geven aan de navigator componenten via de ThemeProvider uit React Navigation. Het thema dat in themeProvider.tsx gedefinieerd is heeft reeds de juiste structuur om door te geven aan de ThemeProvider uit React Navigation.
Omdat een React Navigation theme ook fonts moet definiëren maken we een kopie van het default theme (uit React Navigation). De geïnteresseerde lezer kan de documentatie raadplegen voor meer informatie over hoe de font-settings aangepast kunnen worden.
import {ThemeProvider as NavigationThemeProvider, DefaultTheme} from '@react-navigation/native'
const ThemeProvider: FunctionComponent<PropsWithChildren> = ({children}) => {
const isDark = useColorScheme() === 'dark'
const activeTheme = isDark ? darkTheme : lightTheme
return (
<ThemeContext.Provider value={ {...activeTheme, ...defaultSpacing}}>
<NavigationThemeProvider value={ {...DefaultTheme, ...activeTheme}}>
<View style={[{backgroundColor: activeTheme.colors.background}, styles.container]}>
<StatusBar style="auto"/>
{children}
</View>
</NavigationThemeProvider>
</ThemeContext.Provider>
)
}Nu dat het theme ingesteld is, pas de app zich automatisch aan aan het thema van het operating system.

Stack navigator
De volgende pagina die we uitwerken is de pagina waarop alle taken getoond worden. We kunnen alle taken, zoals vorige les, weergeven op de /tasks/index pagina, maar in plaats daarvan gebruiken we drie aparte pagina's die we definiëren via een Stack navigator.
Voor elk van de drie opties (onafgewerkte taken, alle taken en afgewerkte taken) maken we een nieuwe pagina aan. In elk van de nieuwe routes roepen we de ToDoList component op.
import {type FunctionComponent} from 'react'
import ToDoList from '@/components/tasks/toDoList'
const All: FunctionComponent = () => {
return <ToDoList completedTasks={null} />
}
export default Allimport {type FunctionComponent} from 'react'
import ToDoList from '@/components/tasks/toDoList'
const Completed: FunctionComponent = () => {
return <ToDoList completedTasks={true} />
}
export default Completedimport {type FunctionComponent} from 'react'
import ToDoList from '@/components/tasks/toDoList'
const Todo: FunctionComponent = () => {
return <ToDoList completedTasks={false} />
}
export default TodoAlhoewel we de taken nu kunnen bekijken, is het duidelijk dat er nog enkele problemen zijn.

In tegenstelling tot de index route kunnen we de tasks pagina's niet verbergen via het redirect attribuut omdat deze pagina's wel bezocht moeten kunnen worden. Om dit probleem op te lossen moeten we een nieuwe layout-route toevoegen in de /app/tasks map, waarin we een nieuwe navigator definiëren.
De stack navigator is de default navigator die we ook al gebruikt hebben aan het begin van de les. Dit keer moeten we de navigator echter expliciet toevoegen zodat we titels kunnen toevoegen aan elke pagina. Merk op dat de syntax, behalve de naam van de navigator, identiek is aan die van de tabs navigator.
import {Stack} from 'expo-router'
import {FunctionComponent} from 'react'
const StackLayout: FunctionComponent = () => {
return (
<Stack>
<Stack.Screen
name="all"
options={ {
title: 'All tasks',
}}
/>
<Stack.Screen
name="completed"
options={ {
title: 'Completed task',
}}
/>
<Stack.Screen
name="todo"
options={ {
title: 'Uncompleted tasks',
}}
/>
</Stack>
)
}
export default StackLayoutAls we, na deze aanpassing, de development server opnieuw opstarten (via pnpm android), zien we onderstaande foutmelding in de console.
Nu dat er een layout route toegevoegd is in /app/tasks map, wordt de routing van de /app/tasks map volledig overgenomen door de nieuwe navigator. Dit betekent dat de /tasks/index route niet meer gebruikt kan worden in de tabs navigator, we moeten het tabblad nu naar /tasks laten verwijzen, vervolgens wordt de routing afgehandeld door de stack navigator. Omdat de all component eerst staat, wordt deze automatisch getoond.
const RootLayout: FunctionComponent = () => {
return (
<ThemeProvider>
<Tabs>
<Tabs.Screen name="index" redirect />
<Tabs.Screen
name="tasks"
options={ {
title: 'Tasks',
tabBarIcon: ({color, size}) => <ListTodoIcon size={size} color={color} />,
}}
/>
<Tabs.Screen
name="labels/index"
options={ {
title: 'Labels',
tabBarIcon: ({color, size}) => <TagIcon size={size} color={color} />,
}}
/>
</Tabs>
</ThemeProvider>
)
}Merk op dat /tasks/index.tsx route niets meer doet, bijgevolg kunnen we deze file zonder problemen verwijderen. Als je toch iets wilt doen met deze route, moet je die ook toevoegen aan de stack navigator.

Alhoewel bovenstaande app er al een beetje beter uitziet, is er nog een probleem. Omdat we twee navigators gedefinieerd hebben, worden er nu ook twee headers getoond. Dit is natuurlijk niet de bedoeling, we moeten één van de twee headers verbergen.
Aangezien de stack navigator de titels voor de verschillende tabbladen instelt, is dit de header die we willen tonen. We gebruiken de options property van een navigator screen om de header te verbergen.
const RootLayout: FunctionComponent = () => {
return (
<ThemeProvider>
<Tabs>
<Tabs.Screen name="index" redirect />
<Tabs.Screen
name="tasks"
options={ {
title: 'Tasks',
headerShown: false,
tabBarIcon: ({color, size}) => <ListTodoIcon size={size} color={color} />,
}}
/>
<Tabs.Screen
name="labels/index"
options={ {
title: 'Labels',
tabBarIcon: ({color, size}) => <TagIcon size={size} color={color} />,
}}
/>
</Tabs>
</ThemeProvider>
)
}
useRoute
Momenteel wordt het 'All' tab in de tasks pagina steeds als actief gemarkeerd, ongeacht het geselecteerde tabblad. Via de useRoute hook kunnen we de naam van de huidige route ophalen en zo het actieve tabblad bepalen. Aangezien we in de stack navigator de routes 'all', 'completed' en 'todo' gebruiken, moeten we deze namen ook gebruiken om het actieve tab te bepalen.
import {useRoute} from '@react-navigation/core'
const ToDoList: FunctionComponent<ToDoListProps> = ({completedTasks}) => {
const {tasks} = useTasks()
const filteredTasks = tasks.filter(t => (completedTasks === null ? true : t.completed === completedTasks))
const {colors, margin, padding} = useContext(ThemeContext)
const route = useRoute()
const activeStyle = {backgroundColor: hex2rgba(colors.notification, 0.2), padding: padding.sm, borderRadius: 5}
return (
<>
<View style={[styles.linkContainer, {marginTop: margin.md}]}>
<StyledTitle style={[styles.styledLink, route.name === 'todo' && activeStyle]}>
<Link href="/tasks/todo">To Do</Link>
</StyledTitle>
<StyledTitle style={[styles.styledLink, route.name === 'all' && activeStyle]}>
<Link href="/tasks/all">All</Link>
</StyledTitle>
<StyledTitle style={[styles.styledLink, route.name === 'completed' && activeStyle]}>
<Link href="/tasks/completed">Done</Link>
</StyledTitle>
</View>
<LegendList
data={filteredTasks}
renderItem={({item: task}) => <ToDoItem {...task} key={task.id} />}
keyExtractor={task => task.id}
recycleItems={true}
/>
</>
)
}Info
Merk op dat de navigatie in bovenstaande video relatief traag is. Dit is te verwachten omdat de ToDoList item steeds de taken uitleest via MMKV. Aangezien dit een filesystem operatie is, is dit per definitie traag.
In een echte applicatie gebruik je best een caching mechanisme om data uit te lezen en te herbruiken op de verschillende pagina's.
Back button & animaties
Zoals bovenstaande video illustreert, wordt er voor elke link die in een stack navigator geopend wordt een back button getoond. In de meeste gevallen is dit geen probleem, maar omdat we hier knoppen (To Do, All, Done) gebruiken is het beter als de knop niet getoond wordt.
Via de headerBackVisible property verbergen we de back button. Ter illustratie passen we de animatie ook aan.
const StackLayout: FunctionComponent = () => {
return (
<Stack>
<Stack.Screen
name="all"
options={ {
title: 'All tasks',
headerBackVisible: false,
animation: 'fade',
}}
/>
<Stack.Screen
name="completed"
options={ {
title: 'Completed task',
headerBackVisible: false,
animation: 'fade',
}}
/>
<Stack.Screen
name="todo"
options={ {
title: 'Uncompleted tasks',
headerBackVisible: false,
animation: 'fade',
}}
/>
</Stack>
)
}Modaal venster
De taken zijn nu zichtbaar, maar we kunnen nog geen nieuwe taken toevoegen. Om dit te implementeren gebruiken we een nieuw modaal venster, maar in tegenstelling tot vorige les doen we dit zonder de Modal component.
Expo Router kan, via de Stack navigator een modaal venster tonen. Op Android wordt een modaal venster gewoon getoond als een nieuwe pagina, op iOS wordt een echt modaal venster gebruikt.
import {type FunctionComponent} from 'react'
import TaskForm from '@/components/tasks/taskForm'
const Create: FunctionComponent = () => {
return <TaskForm />
}
export default Createconst StackLayout: FunctionComponent = () => {
return (
<Stack>
<Stack.Screen
name="all"
options={ {
title: 'All tasks',
headerBackVisible: false,
animation: 'fade',
headerRight: ({tintColor}) => (
<Link href="/tasks/create">
<PlusIcon color={tintColor} size={30} />
</Link>
),
}}
/>
<Stack.Screen
name="completed"
options={ {
title: 'Completed task',
headerBackVisible: false,
animation: 'fade',
headerRight: ({tintColor}) => (
<Link href="/tasks/create">
<PlusIcon color={tintColor} size={30} />
</Link>
),
}}
/>
<Stack.Screen
name="todo"
options={ {
title: 'Uncompleted tasks',
headerBackVisible: false,
animation: 'fade',
headerRight: ({tintColor}) => (
<Link href="/tasks/create">
<PlusIcon color={tintColor} size={30} />
</Link>
),
}}
/>
<Stack.Screen
name="create"
options={ {
presentation: 'modal',
title: 'Create task',
}}
/>
</Stack>
)
}Via de useNavigation hook kunnen we de header aanspreken in de TaskForm component. Op deze manier kunnen we een checkmark-knop toevoegen en hieraan een handler koppelen. We kunnen de header niet aanpassen tijdens het renderen, want dat zou tegen de regels van React in gaan. Het is ook niet ideaal om deze code in een useEffect te plaatsen aangezien we de knop dan pas verschijnt nadat de component de eerste keer gerenderd is. Er zou dus enkele milliseconden geen knop aanwezig kunnen zijn.
Door een useLayoutEffect te gebruiken in plaats van een useEffect wordt de knop toegevoegd nog voor de gebruiker iets te zien krijgt. Alhoewel useEffect performanter is (en de voorkeur geniet in de meeste gevallen), wordt deze asynchroon uitgevoerd wanneer de layout geladen is, waardoor het kan zijn dat de pagina eerst toont zonder de knop, en daarna de knop ineens op het scherm 'springt'. useLayoutEffect wordt synchroon uitgevoerd, waardoor de gebruiker pas iets te zien krijgt als alle useLayoutEffects klaar zijn. Dat is meteen ook de reden waarom deze minder performant is.
import {useNavigation} from 'expo-router'
const TaskForm: FunctionComponent<TaskFormProps> = ({id}) => {
const {createTask, getTask, updateTask} = useTasks()
const [name, setName] = useState(getTask(id)?.name ?? '')
const navigation = useNavigation()
// De linting regels heeft geen zin omdat de functie toch veranderd met elke aanpassing aan name.
// Aangezien elke render een gevolg is van een aanpassing aan name, is een useCallback enkel overhead.
// eslint-disable-next-line react-hooks/exhaustive-deps
const handleCreateUpdate = () => {
if (id) {
updateTask({id, name})
} else {
createTask(name)
}
navigation.goBack()
}
useLayoutEffect(() => {
navigation.setOptions({
headerRight: ({tintColor}: {tintColor: string}) => (
<IconButton Icon={CheckIcon} color={tintColor} size={25} onPress={handleCreateUpdate} />
),
})
}, [navigation, handleCreateUpdate])
return (
<>
<StyledTextInput placeholder="Task name" value={name} onChangeText={setName} />
</>
)
}Route parameters
Om de les af te sluiten bouwen we een route waarmee de details van een taak bekeken kunnen worden. Hiervoor moeten we het ID van de taak natuurlijk doorgeven via de URL.
We beginnen met een nieuwe route aan te maken, de naam van dit bestand wordt [taskId], de vierkante haken voegen een navigatieparameter toe aan de route.
Via de useLocalSearchParams hook lezen we de navigatie parameters uit, deze hook werkt op exact dezelfde manier als de useParams hook uit de frontend cursus.
Tenslotte voegen we een link naar de detailpagina toe in de ToDoItem component. Hiervoor gebruiken de we de useRouter hook.
import {FunctionComponent} from 'react'
import TaskForm from '@/components/tasks/taskForm'
import {useLocalSearchParams} from 'expo-router'
const Edit: FunctionComponent = () => {
const {taskId} = useLocalSearchParams<{taskId: string}>()
return <TaskForm id={taskId} />
}
export default Editconst StackLayout: FunctionComponent = () => {
return (
<Stack>
<Stack.Screen
name="all"
options={ {
title: 'All tasks',
headerBackVisible: false,
animation: 'fade',
headerRight: ({tintColor}) => (
<Link href="/tasks/create">
<PlusIcon color={tintColor} size={30} />
</Link>
),
}}
/>
<Stack.Screen
name="completed"
options={ {
title: 'Completed task',
headerBackVisible: false,
animation: 'fade',
headerRight: ({tintColor}) => (
<Link href="/tasks/create">
<PlusIcon color={tintColor} size={30} />
</Link>
),
}}
/>
<Stack.Screen
name="todo"
options={ {
title: 'Uncompleted tasks',
headerBackVisible: false,
animation: 'fade',
headerRight: ({tintColor}) => (
<Link href="/tasks/create">
<PlusIcon color={tintColor} size={30} />
</Link>
),
}}
/>
<Stack.Screen
name="create"
options={ {
presentation: 'modal',
title: 'Create task',
}}
/>
<Stack.Screen
name="[taskId]"
options={ {
title: 'Edit task',
}}
/>
</Stack>
)
}import {useRouter} from 'expo-router'
const ToDoItem: FunctionComponent<ITask> = ({name, id, completed}) => {
const {colors, padding, margin} = useContext(ThemeContext)
const [showActionSheet, setShowActionSheet] = useState<boolean>(false)
const {toggleTaskStatus, deleteTask} = useTasks()
const router = useRouter()
return (
<View style={[styles.container, completed && {borderColor: 'green'}, {marginVertical: margin.sm}]}>
<View style={[styles.labelContainer, {padding: padding.md}]}>
<StyledText style={[styles.label]}>{name}</StyledText>
</View>
<View style={[styles.statusContainer, {padding: padding.sm}]}>
<IconButton
onPress={() => setShowActionSheet(true)}
Icon={EllipsisVerticalIcon}
color={completed ? 'green' : colors.border}
/>
</View>
<ActionSheet
visible={showActionSheet}
actions={[
{
label: `Mark ${name} as ${completed ? 'to-do' : 'completed'}`,
Icon: completed ? CheckCircleIcon : CheckCheckIcon,
onPress: () => toggleTaskStatus(id),
},
{
label: `Delete ${name}`,
Icon: TrashIcon,
onPress: () => deleteTask(id),
},
{
label: 'Details',
Icon: ChevronRightIcon,
onPress: () => router.push(`/tasks/${id}`),
},
]}
onClose={() => setShowActionSheet(false)}
/>
</View>
)
}