Expo Router
expo-router is a navigation library provided by Expo that simplifies the implementation of navigation in React Native applications. It is built on top of React Navigation, a widely used navigation library, and abstracts away much of the complexity involved in managing navigation state and transitions between screens.
Navigation in Expo Router is expressed declaratively, utilizing components to define the flow of the application. This approach makes it intuitive for developers to structure their navigation hierarchy.
Conventional React Native projects typically adopt a structure where a sole root component is commonly specified in either ./App.js or ./index.js. Within the context of Expo Router, an alternative approach is offered through the utilization of the Root Layout, located in app/_layout.tsx
in our Demo. Thereby, the _layout
section of our app handles the overall structure and navigation setup.
// Import global CSS fileimport '../../global.css';
import { BottomSheetModalProvider } from '@gorhom/bottom-sheet';import { ThemeProvider } from '@react-navigation/native';import { Stack } from 'expo-router';import * as SplashScreen from 'expo-splash-screen';import React from 'react';import { StyleSheet } from 'react-native';import FlashMessage from 'react-native-flash-message';import { GestureHandlerRootView } from 'react-native-gesture-handler';import { KeyboardProvider } from 'react-native-keyboard-controller';
import { APIProvider } from '@/api';import { hydrateAuth, loadSelectedTheme } from '@/lib';import { useThemeConfig } from '@/lib/use-theme-config';
export { ErrorBoundary } from 'expo-router';
export const unstable_settings = { initialRouteName: '(app)',};
hydrateAuth();loadSelectedTheme();// Prevent the splash screen from auto-hiding before asset loading is complete.SplashScreen.preventAutoHideAsync();// Set the animation options. This is optional.SplashScreen.setOptions({ duration: 500, fade: true,});
export default function RootLayout() { return ( <Providers> <Stack> <Stack.Screen name="(app)" options={{ headerShown: false }} /> <Stack.Screen name="onboarding" options={{ headerShown: false }} /> <Stack.Screen name="login" options={{ headerShown: false }} /> </Stack> </Providers> );}
function Providers({ children }: { children: React.ReactNode }) { const theme = useThemeConfig(); return ( <GestureHandlerRootView style={styles.container} className={theme.dark ? `dark` : undefined} > <KeyboardProvider> <ThemeProvider value={theme}> <APIProvider> <BottomSheetModalProvider> {children} <FlashMessage position="top" /> </BottomSheetModalProvider> </APIProvider> </ThemeProvider> </KeyboardProvider> </GestureHandlerRootView> );}
const styles = StyleSheet.create({ container: { flex: 1, },});
The Demo app comes with a simple stack and tabs layout. Feel free to remove what is not working for you and add your own using the same approach as the existing ones.
Here is a simple example of the tabs layout.
/* eslint-disable react/no-unstable-nested-components */import { Link, Redirect, SplashScreen, Tabs } from 'expo-router';import React, { useCallback, useEffect } from 'react';
import { Pressable, Text } from '@/components/ui';import { Feed as FeedIcon, Settings as SettingsIcon, Style as StyleIcon,} from '@/components/ui/icons';import { useAuth, useIsFirstTime } from '@/lib';
export default function TabLayout() { const status = useAuth.use.status(); const [isFirstTime] = useIsFirstTime(); const hideSplash = useCallback(async () => { await SplashScreen.hideAsync(); }, []); useEffect(() => { if (status !== 'idle') { setTimeout(() => { hideSplash(); }, 1000); } }, [hideSplash, status]);
if (isFirstTime) { return <Redirect href="/onboarding" />; } if (status === 'signOut') { return <Redirect href="/login" />; } return ( <Tabs> <Tabs.Screen name="index" options={{ title: 'Feed', tabBarIcon: ({ color }) => <FeedIcon color={color} />, headerRight: () => <CreateNewPostLink />, tabBarButtonTestID: 'feed-tab', }} />
<Tabs.Screen name="style" options={{ title: 'Style', headerShown: false, tabBarIcon: ({ color }) => <StyleIcon color={color} />, tabBarButtonTestID: 'style-tab', }} /> <Tabs.Screen name="settings" options={{ title: 'Settings', headerShown: false, tabBarIcon: ({ color }) => <SettingsIcon color={color} />, tabBarButtonTestID: 'settings-tab', }} /> </Tabs> );}
const CreateNewPostLink = () => { return ( <Link href="/feed/add-post" asChild> <Pressable> <Text className="px-3 text-primary-300">Create</Text> </Pressable> </Link> );};
Make sure to check the official docs for more information and examples about expo-router.