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