1--- 2title: Migrate from React Navigation 3sidebar_title: React Navigation 4description: Learn how to migrate a project using React Navigation to Expo Router. 5--- 6 7import { Collapsible } from '~/ui/components/Collapsible'; 8import { FileTree } from '~/ui/components/FileTree'; 9import { BoxLink } from '~/ui/components/BoxLink'; 10import { BookOpen02Icon } from '@expo/styleguide-icons'; 11 12Both React Navigation and Expo Router are Expo frameworks for routing and navigation. Expo Router is a wrapper around React Navigation and many of the concepts are the same. 13 14## Pitch 15 16Along with all the benefits of React Navigation, Expo Router enables automatic deep linking, [type safety](/router/reference/typed-routes), [deferred bundling](/router/reference/async-routes), [static rendering on web](/router/reference/static-rendering), and more. 17 18## Anti-pitch 19 20If your app uses a custom `getPathFromState` or `getStateFromPath` component, it may not be a good fit for Expo Router. If you're using these functions to support [shared routes](/router/advanced/shared-routes) then you should be fine as Expo Router has built-in support for this. 21 22## Recommendations 23 24We recommend making the following modifications to your codebase before beginning the migration: 25 26- Split React Navigation screen components into individual files. For example, if you have `<Stack.Screen component={HomeScreen} />`, then ensure the `HomeScreen` class is in its own file. 27- Convert the project to [TypeScript](/guides/typescript#path-aliases). This will make it easier to spot errors that may occur during the migration. 28- Convert relative imports to [typed aliases](/guides/typescript#path-aliases), for example `../../components/button.tsx` to `@/components/button`, before starting the migration. This makes it easier to move screens around the filesystem without having to update the relative paths. 29- Migrate away from `resetRoot`. This is used to "restart" the app while running. This is generally considered bad practice, and you should restructure your app's navigation so this never needs to happen. 30- Rename the initial route to `index`. Expo Router considers the route that is opened on launch to match `/`, React Navigation users will generally use something such as "Home" for the initial route. 31 32### Refactor search parameters 33 34Refactor screens to [use serializable top-level query parameters](https://reactnavigation.org/docs/params/#what-should-be-in-params). We recommend this in React Navigation as well. 35 36In Expo Router, search parameters can only serializable top-level values such as `number`, `boolean`, and `string`. React Navigation doesn't have the same restrictions, so users can sometimes pass invalid parameters like Functions, Objects, Maps, and so on. 37 38If your code has something similar to the below: 39 40```js 41import { useNavigation } from '@react-navigation/native'; 42 43const navigation = useNavigation(); 44 45navigation.push('Followers', { 46 onPress: profile => { 47 navigation.push('User', { profile }); 48 }, 49}); 50``` 51 52Consider restructuring so the function can be accessed from the "followers" screen. In this case, you can access the router and push directly from the "followers" screen. 53 54### Eagerly load UI 55 56It's common in React Native apps to `return null` from the root component while assets and fonts are loading. This is bad practice and generally unsupported in Expo Router. If you absolutely must defer rendering, then ensure you don't attempt to navigate to any screens. 57 58Historically this pattern exists because React Native will throw errors if you use custom fonts that haven't loaded yet. We changed this upstream in React Native 0.72 (SDK 49) so the default behavior is to swap the default font when the custom font loads. If you'd like to hide individual text elements until a font has finished loading, write a wrapper `<Text>`, which returns null until the font has loaded. 59 60On web, returning `null` from the root will cause [static rendering](/router/reference/static-rendering) to skip all of the children, resulting in no searchable content. This can be tested by using "View Page Source" in Chrome, or by disabling JavaScript and reloading the page. 61 62## Migration 63 64### Delete unused or managed code 65 66Expo Router automatically adds `react-native-gesture-handler` (when installed) and `react-native-safe-area-context` support. 67 68```diff 69- import { GestureHandlerRootView } from 'react-native-gesture-handler'; 70- import { SafeAreaProvider } from "react-native-safe-area-context"; 71 72export default function App() { 73 return ( 74- <GestureHandlerRootView style={{ flex: 1 }}> 75- <SafeAreaProvider> 76 <MyApp /> 77- </SafeAreaProvider> 78- </GestureHandlerRootView> 79 ) 80} 81``` 82 83### Copying screens to the app directory 84 85Create an **app** directory at the root of your repo, or in a root **src** directory. 86 87Layout the structure of your app by creating files according to the [creating pages guide](/routing/create-pages/). Best practice dictates that you use kebab-case and lowercase letters for filenames. 88 89Replace navigators with directories, for example: 90 91{/* prettier-ignore */} 92```jsx React Navigation 93function HomeTabs() { 94 return ( 95 <Tab.Navigator> 96 <Tab.Screen name="Home" component={Home} /> 97 <Tab.Screen name="Feed" component={Feed} /> 98 </Tab.Navigator> 99 ); 100} 101 102function App() { 103 return ( 104 /* @info NavigationContainer is managed by Expo Router. */ 105 <NavigationContainer 106 /* @end */ 107 108 /* @info Delete the linking configuration, this is managed by Expo Router. */ 109 linking={ 110 { 111 // ... 112 } 113 } 114 /* @end */ 115 > 116 <Stack.Navigator> 117 /* @info Standard screens can be moved to files. */ 118 <Stack.Screen name="Settings" component={Settings} /> 119 /* @end */ 120 <Stack.Screen name="Profile" component={Profile} /> 121 /* @info A screen that exports a navigator should be converted to a directory with a layout route. */ 122 <Stack.Screen 123 /* @end */ 124 name="Home" 125 component={HomeTabs} 126 options={{ 127 title: 'Home Screen', 128 }} 129 /> 130 </Stack.Navigator> 131 </NavigationContainer> 132 ); 133} 134``` 135 136**Expo Router:** 137 138- Rename the "main" route from **Home** to **index** to ensure it matches the `/` path. 139- Convert names to lowercase. 140- Move all the screens to the appropriate file locations inside the app directory. This may take some experimenting. 141 142<FileTree 143 files={[ 144 'app/_layout.js', 145 'app/(home)/_layout.js', 146 'app/(home)/index.js', 147 'app/(home)/feed.js', 148 'app/profile.js', 149 'app/settings.js', 150 ]} 151/> 152 153```jsx app/_layout.js 154import { Stack } from 'expo-router'; 155 156export default function RootLayout() { 157 return ( 158 <Stack> 159 <Stack.Screen 160 name="(home)" 161 /* @info Options can be expressed using the Screen component on a navigator. This has the same types as React Navigation. */ 162 options={{ 163 title: 'Home Screen', 164 }} 165 /* @end */ 166 /> 167 </Stack> 168 ); 169} 170``` 171 172The tab navigator will be moved to a subdirectory. 173 174```js app/(home)/_layout.js 175import { Tabs } from 'expo-router'; 176 177export default function HomeLayout() { 178 return <Tabs />; 179} 180``` 181 182### Using Expo Router hooks 183 184React Navigation v6 and lower will pass the props `{ navigation, route }` to every screen. This pattern is going away in React Navigation, but we never introduced it to the Expo Router. 185 186Instead, migrate `navigation` to the `useRouter` hook. 187 188```diff 189+ import { useRouter } from 'expo-router'; 190 191export default function Page({ 192- navigation 193}) { 194- navigation.push('User', { user: 'bacon' }); 195 196+ const router = useRouter(); 197+ router.push('/users/bacon'); 198} 199``` 200 201Similarly, migrate from the `route` prop to the `useLocalSearchParams` hooks. 202 203```diff 204+ import { useLocalSearchParams } from 'expo-router'; 205 206export default function Page({ 207- route 208}) { 209- const user = route?.params?.user; 210 211+ const { user } = useLocalSearchParams(); 212} 213``` 214 215### Migrating the Link component 216 217React Navigation and Expo Router both provide Link components. However, Expo's Link component uses `href` instead of [`to`](https://reactnavigation.org/docs/use-link-props#to). 218 219```jsx 220// React Navigation 221<Link to="Settings" /> 222 223// Expo Router 224<Link href="/settings" /> 225``` 226 227React Navigation users will often create a custom Link component with the `useLinkProps` hook to control the child component. This isn't necessary in Expo Router, instead, use the `asChild` prop. 228 229### Sharing screens across navigators 230 231It's common for React Navigation apps to reuse a set of routes across multiple navigators. This is generally used with tabs to ensure each tab can push any screen. 232 233In Expo Router, you can either migrate to [shared routes](/router/advanced/shared-routes) or create multiple files and re-export the same component from them. 234 235When you use groups or shared routes, you can navigate to specific tabs by using the fully qualified route name, for example, `/(home)/settings` instead of `/settings`. 236 237### Migrating screen tracking events 238 239You may have your screen tracking setup according to our [React Navigation screen tracking guide](https://reactnavigation.org/docs/screen-tracking/), update it according to the [Expo Router screen tracking guide](/router/reference/screen-tracking). 240 241### Using platform-specific components for screens 242 243Refer to the [platform-specific modules](/router/advanced/platform-specific-modules) guide for info on switching UI based on the platform. 244 245### Replacing the NavigationContainer 246 247The global React Navigation [`<NavigationContainer />`](https://reactnavigation.org/docs/navigation-container/) is completely managed in Expo Router. Expo Router provides systems for achieving the same functionality as the `NavigationContainer` without needing to use it directly. 248 249<Collapsible summary="API substitutions"> 250 251### Ref 252 253The `NavigationContainer` ref should not be accessed directly. Use the following methods instead. 254 255#### `resetRoot` 256 257Navigate to the initial route of the application. For example, if your app starts at `/` (recommended), then you can replace the current route with `/` using this method. 258 259```jsx 260import { useRouter } from 'expo-router'; 261 262function Example() { 263 const router = useRouter(); 264 265 return ( 266 <Text 267 onPress={() => { 268 // Go to the initial route of the application. 269 router.replace('/'); 270 }}> 271 Reset App 272 </Text> 273 ); 274} 275``` 276 277#### `getRootState` 278 279Use `useRootNavigationState()`. 280 281#### `getCurrentRoute` 282 283Unlike React Navigation, Expo Router can reliably represent any route with a string. Use the [`usePathname()`](/router/reference/hooks/#usepathname) or [`useSegments()`](/router/reference/hooks/#usesegments) hooks to identify the current route. 284 285#### `getCurrentOptions` 286 287Use the [`useLocalSearchParams()`](/router/reference/hooks/#uselocalsearchparams) hook to get the current route's query parameters. 288 289#### `addListener` 290 291The following events can be migrated: 292 293#### `state` 294 295Use the [`usePathname()`](/router/reference/hooks/#usepathname) or [`useSegments()`](/router/reference/hooks/#usesegments) hooks to identify the current route. Use in conjunction with `useEffect(() => {}, [...])` to observe changes. 296 297#### `options` 298 299Use the [`useLocalSearchParams()`](/router/reference/hooks/#uselocalsearchparams) hook to get the current route's query parameters. Use in conjunction with `useEffect(() => {}, [...])` to observe changes. 300 301### props 302 303Migrate the following `<NavigationContainer />` props: 304 305#### `initialState` 306 307In Expo Router, you can rehydrate your application state from a route string (for example, `/user/evanbacon`). Use [redirects](/router/reference/redirects/) to handle initial states. See [shared routes](/router/advanced/shared-routes/) for advanced redirects. 308 309Avoid using this pattern in favor of deep linking (for example, a user opens your app to `/profile` rather than from the home screen) as it is most analogous to the web. If an app crashes due to a particular screen, it's best to avoid automatically navigating back to that exact screen when the app starts as it may require reinstalling the app to fix. 310 311#### `onStateChange` 312 313Use the [`usePathname()`](/router/reference/hooks/#usepathname), [`useSegments()`](/router/reference/hooks/#usesegments), and [`useGlobalSearchParams()`](/router/reference/hooks/#useglobalsearchparams) hooks to identify the current route state. Use in conjunction with `useEffect(() => {}, [...])` to observe changes. 314 315- If you're attempting to track screen changes, follow the [Screen Tracking guide](/router/reference/screen-tracking/). 316- React Navigation recommends avoiding [`onStateChange`](https://reactnavigation.org/docs/navigation-container/#onstatechange). 317 318#### `onReady` 319 320In React Navigation, [`onReady`](https://reactnavigation.org/docs/navigation-container/#onready) is most often used to determine when the splash screen should hide or when to track screens using analytics. Expo Router has special handling for both of these use cases. Assume the navigation is always ready for navigation events in the Expo Router. 321 322- See the [Screen Tracking guide](/router/reference/screen-tracking/) for info on migrating analytics from React Navigation. 323- See the [Splash Screen feature](/routing/appearance/#splash-screen) for info on handling the splash screen. 324 325#### `onUnhandledAction` 326 327Actions are always handled in Expo Router. Use [dynamic routes](/routing/create-pages/#dynamic-routes) and [404 screens](/routing/error-handling/#unmatched-routes) in favor of [`onUnhandledAction`](https://reactnavigation.org/docs/navigation-container/#onunhandledaction). 328 329#### `linking` 330 331The [`linking`](https://reactnavigation.org/docs/navigation-container/#linking) prop is automatically constructed based on the files to the `app/` directory. 332 333#### `fallback` 334 335The [`fallback`](https://reactnavigation.org/docs/navigation-container/#fallback) prop is automatically handled by Expo Router. Learn more in the [Splash Screen](/routing/appearance/#splash-screen) guide. 336 337#### `theme` 338 339In React Navigation, you set the theme for the entire app using the [`<NavigationContainer />`](https://reactnavigation.org/docs/navigation-container/#theme) component. Expo Router manages the root container for you, so instead you should set the theme using the `ThemeProvider` directly. 340 341```jsx app/_layout.tsx 342import { ThemeProvider, DarkTheme, DefaultTheme, useTheme } from '@react-navigation/native'; 343import { Slot } from 'expo-router'; 344 345export default function RootLayout() { 346 return ( 347 /* @info All layouts inside this provider will use the dark theme. */ 348 <ThemeProvider value={DarkTheme}> 349 /* @end */ 350 <Slot /> 351 </ThemeProvider> 352 ); 353} 354``` 355 356You can use this technique at any layer of the app to set the theme for a specific layout. The current theme can be accessed with the `useTheme` hook from `@react-navigation/native`. 357 358#### `children` 359 360The `children` prop is automatically populated based on the files in the **app/** directory and the currently open URL. 361 362#### `independent` 363 364Expo Router does not support [`independent`](https://reactnavigation.org/docs/navigation-container/#independent) containers. This is because the router is responsible for managing the single `<NavigationContainer />`. Any additional containers will not be automatically managed by Expo Router. 365 366#### `documentTitle` 367 368Use the [Head component](/router/reference/static-rendering#meta-tags) to set the webpage title. 369 370#### `ref` 371 372Use the `useRootNavigation()` hook instead. 373 374</Collapsible> 375 376### Rewriting custom navigators 377 378If your project has a custom navigator, you can rewrite this or port it to Expo Router. 379 380To port, simply use the `withLayoutContext` function: 381 382```js 383import { createCustomNavigator } from './my-navigator'; 384 385export const CustomNavigator = withLayoutContext(createCustomNavigator().Navigator); 386``` 387 388To rewrite, use the `Navigator` component, which wraps the [`useNavigationBuilder`](https://reactnavigation.org/docs/custom-navigators#usenavigationbuilder) hook from React Navigation. 389 390The return value of `useNavigationBuilder` can be accessed with the `Navigator.useContext()` hook from inside the `<Navigator />` component. Properties can be passed to `useNavigationBuilder` using the props of the `<Navigator />` component, this includes `initialRouteName`, `screenOptions`, `router`. 391 392All of the `children` of a `<Navigator />` component will be rendered as-is. 393 394- `Navigator.useContext`: Access the React Navigation `state`, `navigation`, `descriptors`, and `router` for the custom navigator. 395- `Navigator.Slot`: A React component used to render the currently selected route. This component can only be rendered inside a `<Navigator />` component. 396 397#### Example 398 399Custom layouts have an internal context that is ignored when using the `<Slot />` component without a `<Navigator />` component wrapping it. 400 401{/* prettier-ignore */} 402```jsx 403import { View } from 'react-native'; 404import { TabRouter } from '@react-navigation/native'; 405 406import { Navigator, usePathname, Slot, Link } from 'expo-router'; 407 408export default function App() { 409 return ( 410 /* @info */ 411 <Navigator router={TabRouter}> 412 /* @end */ 413 <Header /> 414 <Slot /> 415 </Navigator> 416 ); 417} 418 419function Header() { 420 const { navigation, state, descriptors, router } = Navigator.useContext(); 421 const pathname = usePathname(); 422 423 return ( 424 <View> 425 <Link href="/">Home</Link> 426 <Link 427 href="/profile" 428 /* @info Use <code>pathname</code> to determine if the link is active. */ 429 style={[pathname === '/profile' && { color: 'blue' }]}> 430 /* @end */ 431 Profile 432 </Link> 433 <Link href="/settings">Settings</Link> 434 </View> 435 ); 436} 437``` 438 439### Use Expo Router's Splash Screen wrapper 440 441Expo Router wraps `expo-splash-screen` and adds special handling to ensure it's hidden after the navigation mounts, and whenever an unexpected error is caught. Simply migrate from importing `expo-splash-screen` to importing `SplashScreen` from `expo-router`. 442 443### Observing the navigation state 444 445If you're observing the navigation state directly, consider migrating to the `usePathname`, `useSegments`, and `useGlobalSearchParams` hooks. 446 447### Passing params to nested screens 448 449Instead of using the [nested screen navigation events](https://reactnavigation.org/docs/params/#passing-params-to-nested-navigators), use a qualified href: 450 451```js 452// React Navigation 453navigation.navigate('Account', { 454 screen: 'Settings', 455 params: { user: 'jane' }, 456}); 457 458// Expo Router 459router.push({ pathname: '/account/settings', params: { user: 'jane' } }); 460``` 461 462### Setting initial routes for deep linking and server navigation 463 464In React Navigation, you can use the `initialRouteName` property of the linking configuration. In Expo Router, use [layout settings](/router/advanced/router-settings). 465 466### Migrating TypeScript types 467 468Expo Router can automatically generate [statically typed routes](/router/reference/typed-routes), this will ensure you can only navigate to valid routes. 469