In React Navigation 7, what is the difference between the static API with createStaticNavigation and the dynamic API inside NavigationContainer?
answer
- an object versus JSX
- screens and groups keys
- types inferred with StaticParamList
- linking enabled: 'auto'
- createStaticNavigation wraps NavigationContainer
basics
~20 sThe static API describes navigators as configuration objects and turns the root into a component with createStaticNavigation, which infers types and can generate deep-link paths. The dynamic API renders Navigator and Screen components inside NavigationContainer, trading that automation for runtime flexibility.
solid answer
~50 sWith the static API you call `createNativeStackNavigator({ screens: { Home: HomeScreen, Player: { screen: PlayerScreen, options } }, screenOptions })` and pass the root to `createStaticNavigation`, which returns a `Navigation` component that wraps `NavigationContainer`. Because the tree is a plain object, `StaticParamList<typeof RootStack>` infers the param types and `linking={{ enabled: 'auto', prefixes }}` generates kebab-case paths for leaf screens. Conditional screens use an `if` hook on a screen or group. The dynamic API renders `<Stack.Navigator>` with `<Stack.Screen name component options />` children inside `<NavigationContainer>`; screens and options are ordinary JSX and props, so anything computed at render time is possible, but you write the param list type and the linking config yourself. The static API was introduced in React Navigation 7 and is built on top of the dynamic one, so they can be combined.
code
tsx · 25 linesimport { createStaticNavigation, type StaticParamList, type StaticScreenProps } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { Text } from 'react-native';
function HomeScreen() {
return <Text>Continue listening</Text>;
}
function PlayerScreen({ route }: StaticScreenProps<{ bookId: string }>) {
return <Text>Playing {route.params.bookId}</Text>;
}
const RootStack = createNativeStackNavigator({
screens: {
Home: HomeScreen,
Player: { screen: PlayerScreen, options: { presentation: 'modal' } },
},
});
export type RootStackParamList = StaticParamList<typeof RootStack>;
const Navigation = createStaticNavigation(RootStack);
export default function App() {
return <Navigation linking={{ enabled: 'auto', prefixes: ['audiobooks://'] }} />;
}go deeper
Recall the two shapes: a config object passed to createStaticNavigation, or Navigator and Screen components inside NavigationContainer.
Explain what the static API automates, StaticParamList typing and auto-generated paths, and what the dynamic API allows in exchange.
Pick the style per app or per navigator, migrate a v6-style dynamic tree incrementally, and avoid double containers and hand-maintained link maps drifting from the tree.
Decide a team convention for configuration style that keeps types and deep links trustworthy as the navigation tree grows.
## Two ways to describe the same tree React Navigation 7 offers two configuration styles that produce the same navigators at runtime. | | Static API | Dynamic API | |---|---|---| | Navigator definition | `createXNavigator({ screens, groups, screenOptions })` | `const X = createXNavigator()` then `<X.Navigator>` JSX | | Root | `createStaticNavigation(RootNavigator)` returns a component | `<NavigationContainer>` wraps the navigators | | Param types | Inferred: `StaticParamList<typeof Root>` | Written by hand as a param list type | | Deep-link paths | Can be generated: `linking.enabled: 'auto'` | A `linking.config.screens` map you write | | Conditional screens | `if` hook on a screen or group | Ordinary conditional JSX | ## The static API A navigator factory called **with a configuration object** returns a static navigator: - `screens` maps route names to either a component or an object with `screen`, `options`, `linking`, `if` and other screen props; a screen can also be another static navigator. - `groups` bundles screens with shared `screenOptions` or a shared `if` condition. - Navigator-level props such as `screenOptions`, `initialRouteName` and `screenLayout` sit next to `screens`. `createStaticNavigation(tree)` then builds a `Navigation` component. It is a wrapper around `NavigationContainer`, so it accepts the container's props (`theme`, `onReady`, `onStateChange` and others), except that `linking` takes a slimmer shape: you give `prefixes` and an `enabled` flag, and the screens map is derived from the tree. With `enabled: 'auto'`, every leaf screen gets a kebab-case path unless it defines its own `linking`. The biggest practical win is **typing**. Because the tree is a value, `type RootStackParamList = StaticParamList<typeof RootStack>` extracts every route and its params, including nested navigators; screens declare their params through `StaticScreenProps<{ bookId: string }>`. ## The dynamic API The dynamic API is component-based: 1. `const Tab = createBottomTabNavigator()`. 2. Render `<NavigationContainer>` at the root. 3. Inside it, `<Tab.Navigator screenOptions={...}>` with `<Tab.Screen name="Home" component={HomeScreen} />` children, optionally grouped with `<Tab.Group>`. Everything is JSX evaluated at render time, so screen lists, options and even which navigator you render can depend on props, context or state. The cost is bookkeeping: you declare a param list type for each navigator and keep a `linking.config.screens` map in sync with the tree yourself. ## What both styles share - **The same navigators and options.** `presentation`, `headerShown`, `tabBarIcon` and every other option mean the same thing in both styles. - **The same container props.** The component from `createStaticNavigation` accepts `NavigationContainer`'s props such as `theme`, `onReady` and `onStateChange`; only `linking` has a slimmer shape. - **The same runtime.** `useNavigation`, `navigation.navigate` and the navigation state behave identically, because the static API renders the dynamic components underneath. ## Choosing - **New apps and new navigators**: the static API is the default in React Native's own navigation guide and removes two sources of drift, types and linking. - **Highly dynamic trees**, such as screens generated from server configuration or navigators whose screen list depends on runtime data beyond a boolean condition: the dynamic API fits better. - **Migration**: an existing v6-style dynamic tree keeps working in v7. The static API is implemented on top of the dynamic one, so a static navigator can render inside a dynamic tree (its `getComponent()` returns a component) and a dynamic navigator component can be a screen in a static config. ## Audiobook example The audiobook app's root could be a static native stack whose first screen is the Home, Search and Library tabs and whose `Player` screen has `options: { presentation: 'modal' }`. `StaticParamList` then types `navigation.navigate('Player', { bookId })` everywhere, and `enabled: 'auto'` gives the player a `/player` path without a hand-written map. ## Pitfalls - **Forgetting that `createStaticNavigation` already renders a container.** Wrapping its result in another `NavigationContainer` nests containers, and React Navigation throws an error for a nested container that is not wrapped in `NavigationIndependentTree`. - **Expecting hooks in the static config object.** Options that depend on the route or theme use a function, `options: ({ route, theme }) => ({ ... })`; per-render logic belongs in the screen component, for example with `navigation.setOptions`.
- Can you pass a full linking.config.screens map to the component returned by createStaticNavigation?No. Its `linking` prop omits the `screens` map, because the paths come from the static tree: from each screen's own `linking` entry, or generated for leaf screens when `enabled: 'auto'`. You still pass `prefixes` and can set `config.path` and `config.initialRouteName`.
- How does the static API show or hide screens based on whether the user is signed in?A screen or group in the static config takes an `if` property, a hook such as `useIsSignedIn` that returns a boolean; the screen is rendered only when it returns `true`. The flow design around it is a separate topic.
saying these in an interview costs you the question
- createStaticNavigation still needs to be wrapped in a NavigationContainer.
- The static API cannot express nested navigators.
- Static and dynamic navigators can never be combined in one app.
- The dynamic API infers route param types automatically.
- linking enabled: 'auto' requires you to list every path by hand.