@react-navigation/native
@react-navigation/native is the runtime foundation of React Navigation for React Native and web. It owns the top-level navigation container, state, linking, themes, focus hooks, refs, route-path conversion, back-button integration, and TypeScript helpers. It does not render a stack, tabs, or drawer by itself. You add a navigator package such as @react-navigation/native-stack or @react-navigation/bottom-tabs, then place that navigator under the container or use version 7's static configuration API.
The default choice for custom React Native navigation, with excellent composition and a real cost in companion packages and platform configuration. Expo projects that prefer filesystem routes should start with Expo Router instead of assembling this layer directly.
Use it if
- You need composable stack, tab, drawer, and modal navigation in a React Native application
- You want URL and app-link mapping, navigation-state persistence, themes, and focus lifecycle from one ecosystem
- You need a dynamic component API or version 7's static configuration with inferred TypeScript route parameters
- Your application targets Expo, bare React Native, or React Native Web and can install platform dependencies
- You expect one package to provide visible navigation UI: native only supplies the container and core integration, while stacks, tabs, drawers, gestures, and elements live in additional packages
- You want file-based routing and universal URL conventions in an Expo app: Expo Router sits on React Navigation but supplies route files, layouts, typed links, and platform integration
- You cannot do native dependency setup: the getting-started guide requires react-native-screens and react-native-safe-area-context for most navigators, plus CocoaPods installation in bare iOS projects
- You need deep links without platform work: iOS associated domains, Android intent filters, URL schemes, web hosting, and a rebuilt installed binary remain outside this JavaScript package
- You want the repository main branch to match the installed stable major: npm is on 7.3.16 while the repo README says main contains the upcoming version 8 and directs stable users to the 7.x branch
Setup reality
Installing @react-navigation/native is the first step, not the whole setup. Version 7.3.16 peers on React 18.2 or newer and React Native, and its package dependencies provide the core state machinery. Most actual navigators also require react-native-screens and react-native-safe-area-context. In Expo, use npx expo install for those native packages so versions match the Expo SDK. In a bare React Native app, install them with your package manager and run npx pod-install ios on macOS; Android and older React Native versions may need the platform setup documented by each dependency. Then install at least one navigator, such as @react-navigation/native-stack, bottom-tabs, drawer, stack, or material-top-tabs. Some of those add react-native-gesture-handler or react-native-reanimated and their own Babel or native requirements. Dynamic configuration wraps the root navigator in one NavigationContainer. Static configuration instead builds a navigator object and passes it through createStaticNavigation, which supplies its own container. Do not nest ordinary containers to isolate feature flows; navigator nesting and screen groups are usually the right model. Route params should contain minimal serializable identifiers, not entire domain objects, because navigation state may be persisted or encoded into URLs. Deep linking is not just a linking object. Custom schemes, Universal Links, Android App Links, domain association files, and a rebuilt binary all need separate platform work, and deferred deep linking after installation needs another service or custom infrastructure. If you provide initialState, the container source says initial deep-link state will not be handled, so persistence code must skip restoration when a URL launched the app and should usually skip custom state on web. Navigation refs are an escape hatch for code outside components; they are not ready immediately, do not replace screen hooks, and can make linking and test flows harder. Focus is a navigation lifecycle rather than React mount lifecycle, so subscriptions and data refreshes belong in useFocusEffect with memoized callbacks and cleanup. Back interception and unsaved-form protection must redispatch the blocked action after confirmation. Finally, verify examples against the version 7 docs or 7.x branch because the repository's default branch now describes the upcoming major version 8.
Patterns
Create a dynamic native stackcreate-native-stack
import { NavigationContainer } from '@react-navigation/native'
import { createNativeStackNavigator } from '@react-navigation/native-stack'
const Stack = createNativeStackNavigator()
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Details" component={DetailsScreen} />
</Stack.Navigator>
</NavigationContainer>
)
}Install @react-navigation/native-stack separately; the native package does not render a stack.
Build a version 7 static navigatoruse-static-navigation
import { createStaticNavigation } from '@react-navigation/native'
import { createNativeStackNavigator } from '@react-navigation/native-stack'
const RootStack = createNativeStackNavigator({
screens: {
Home: HomeScreen,
Details: DetailsScreen,
},
})
const Navigation = createStaticNavigation(RootStack)
export default function App() {
return <Navigation />
}createStaticNavigation supplies the root container; do not wrap Navigation in another NavigationContainer.
Navigate with serializable route paramsnavigate-with-params
function HomeScreen({ navigation }) {
return (
<Button
title="Open profile"
onPress={() => navigation.navigate('Profile', { userId: 'jane' })}
/>
)
}
function ProfileScreen({ route }) {
return <Text>User: {route.params.userId}</Text>
}Pass identifiers and small serializable values, then load domain data in the destination screen.
Map app links to screensconfigure-deep-links
const linking = {
prefixes: ['myapp://', 'https://app.example.com'],
config: {
screens: {
Home: '',
Profile: {
path: 'users/:userId',
},
NotFound: '*',
},
},
}
<NavigationContainer linking={linking} fallback={<Loading />}>
<RootNavigator />
</NavigationContainer>The JavaScript mapping is only one layer; configure iOS and Android associations or schemes and rebuild the installed app.
Extend the navigation themeset-theme
import { DarkTheme, NavigationContainer } from '@react-navigation/native'
const theme = {
...DarkTheme,
colors: {
...DarkTheme.colors,
primary: '#7dd3fc',
background: '#0f172a',
card: '#111827',
},
}
<NavigationContainer theme={theme}>
<RootNavigator />
</NavigationContainer>Extend every colors object rather than replacing it, or required theme colors disappear.
Refresh data when a screen is focusedrun-on-focus
import * as React from 'react'
import { useFocusEffect } from '@react-navigation/native'
function OrdersScreen() {
useFocusEffect(
React.useCallback(() => {
const controller = new AbortController()
loadOrders({ signal: controller.signal })
return () => controller.abort()
}, []),
)
return <OrdersList />
}Wrap the callback in useCallback; otherwise a new function can rerun the effect on every focused render.
Confirm before discarding unsaved changesprevent-leaving
import { Alert } from 'react-native'
import { usePreventRemove } from '@react-navigation/native'
usePreventRemove(hasUnsavedChanges, ({ data }) => {
Alert.alert('Discard changes?', 'Your edits will be lost.', [
{ text: 'Stay', style: 'cancel' },
{
text: 'Discard',
style: 'destructive',
onPress: () => navigation.dispatch(data.action),
},
])
})Redispatch the captured action only after confirmation; calling goBack can lose the original remove action's intent.
Use a guarded root navigation refnavigate-outside-component
import { createNavigationContainerRef } from '@react-navigation/native'
export const navigationRef = createNavigationContainerRef()
export function openInbox() {
if (navigationRef.isReady()) {
navigationRef.navigate('Inbox')
}
}
<NavigationContainer ref={navigationRef}>
<RootNavigator />
</NavigationContainer>Prefer useNavigation inside screens; refs are for exceptional code paths and must be checked for readiness.
Infer route params from static configurationtype-static-routes
import type { StaticParamList } from '@react-navigation/native'
type RootStackParamList = StaticParamList<typeof RootStack>
declare global {
namespace ReactNavigation {
interface RootParamList extends RootStackParamList {}
}
}The static API can infer route names and params; dynamic configuration usually needs an explicit param-list type.
Persist container state on native platformspersist-navigation-state
const [initialState, setInitialState] = React.useState()
const [ready, setReady] = React.useState(false)
React.useEffect(() => {
AsyncStorage.getItem('NAVIGATION_STATE').then((value) => {
if (value) setInitialState(JSON.parse(value))
setReady(true)
})
}, [])
if (!ready) return <Loading />
return (
<NavigationContainer
initialState={initialState}
onStateChange={(state) =>
AsyncStorage.setItem('NAVIGATION_STATE', JSON.stringify(state))
}
>
<RootNavigator />
</NavigationContainer>
)Do not restore custom initialState when an incoming deep link should define startup state, and avoid this pattern on web where the URL is the state source.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| expo-router | npm | You use Expo and want file-based routes, layouts, URL-first navigation, and React Navigation underneath |
| react-native-navigation | npm | You prefer Wix's native-navigation architecture and accept heavier platform-specific setup |
| solito | npm | You share screens between React Native and Next.js and need an adapter across native and web routing |