skip to content

In a React Navigation 7 TypeScript app, how do you type a settings screen nested in the Profile tab so it can navigate to root screens?

level: seniorimportance: should knowfreq 30%

answer

  1. NavigatorScreenParams in the parent list
  2. CompositeScreenProps, own navigator first
  3. compose recursively up to the root
  4. static config infers nesting
  5. screen names checked at every level

basics

~20 s

Declare the child list inside the parent's with NavigatorScreenParams, then type the nested screen with CompositeScreenProps: its own navigator's props first, then the parent's, composed again up to the root. Static configuration infers the nested params automatically.

solid answer

~40 s

Each navigator gets its own param list, and the parent's list declares the nesting: `Profile: NavigatorScreenParams<ProfileStackParamList> | undefined` in the tab list, `MainTabs: NavigatorScreenParams<MainTabParamList> | undefined` in the root list. That makes `navigate('Profile', { screen: 'Settings' })` check the child name and its params. The nested screen's props combine navigators with `CompositeScreenProps`: first `NativeStackScreenProps<ProfileStackParamList, 'Settings'>`, then `CompositeScreenProps<BottomTabScreenProps<MainTabParamList, 'Profile'>, NativeStackScreenProps<RootStackParamList>>`. The resulting `navigation` knows its own stack's `push`, the tabs' `jumpTo` and the root's screens such as `Chat`, while `route`, options and events stay the settings screen's own. With the static API, a nested static navigator used as a screen is inferred as `NavigatorScreenParams` by `StaticParamList`, so none of this is written by hand.

go deeper

for a junior

Recall that a parent's param list marks a nested navigator with NavigatorScreenParams so navigate with screen and params is type-checked.

for a middle

Explain how CompositeScreenProps merges a nested screen's navigator with its parents and why the screen's own navigator comes first.

for a senior

Show you keep composite chains in step with the tree, or move to static configuration so StaticParamList infers nesting and drift disappears.

for a principal

Choose the typing strategy for a large tree, static inference or hand-written composites, weighing flexibility of dynamic screens against the cost of drift.

## Why nesting needs extra types In **React Navigation 7** with TypeScript, a param list describes one navigator. A screen nested two levels deep can call methods that belong to three navigators: its own stack, the tab navigator above it and the root stack above that. The types have to say both **what params a nested screen takes when addressed from a parent** and **which navigators a nested screen can talk to**. The dating app used here: ```text RootStack: MainTabs, Chat, Paywall MainTabs: Discover, Matches, Profile Profile: ProfileHome, Settings, NotificationSettings (stack) ``` ## Declaring nesting with NavigatorScreenParams A screen that renders a navigator takes params of type **`NavigatorScreenParams<ChildParamList>`**: ```ts type ProfileStackParamList = { ProfileHome: undefined; Settings: undefined; NotificationSettings: { section: 'matches' | 'messages' }; }; type MainTabParamList = { Discover: undefined; Matches: undefined; Profile: NavigatorScreenParams<ProfileStackParamList> | undefined; }; type RootStackParamList = { MainTabs: NavigatorScreenParams<MainTabParamList> | undefined; Chat: { matchId: string }; Paywall: undefined; }; ``` `NavigatorScreenParams` is a union: either `{ screen, params?, initial?, merge?, pop?, path? }` for one child screen, with `params` required when that child requires them, or `{ state }` for a whole nested state. With it: - `navigate('Profile', { screen: 'NotificationSettings', params: { section: 'matches' } })` compiles; - misspelling the screen, or omitting `section`, fails to compile; - `| undefined` allows `navigate('Profile')` to open the tab on its current screen. ## Combining navigators with CompositeScreenProps The nested screen's props are built with **`CompositeScreenProps<A, B>`**, where `A` is the screen's own navigator and `B` its parent. The `route` comes from `A`; the `navigation` merges both. For a deeper tree you nest the composition: 1. `NativeStackScreenProps<ProfileStackParamList, 'Settings'>`, the screen itself; 2. `BottomTabScreenProps<MainTabParamList, 'Profile'>`, the tab that hosts the stack; 3. `NativeStackScreenProps<RootStackParamList>`, the root stack. ```ts type SettingsScreenProps = CompositeScreenProps< NativeStackScreenProps<ProfileStackParamList, 'Settings'>, CompositeScreenProps< BottomTabScreenProps<MainTabParamList, 'Profile'>, NativeStackScreenProps<RootStackParamList> > >; ``` The **order matters**: the first type supplies the route name and state, the options that `setOptions` accepts and the events that `addListener` accepts, while the param lists of both are combined so `navigate` can reach screens in either. The screen's own navigator therefore goes first. With the result: - `navigation.push('NotificationSettings', …)` comes from the own stack; - `navigation.jumpTo('Discover')` comes from the tabs; - `navigation.navigate('Chat', { matchId })` type-checks against the root list. The same composite type works for hooks: `useNavigation<SettingsScreenProps['navigation']>()`. ## Typing hooks deep in the tree Components below the screen use hooks, which cannot infer their position. Three patterns cover them: - `useNavigation<SettingsScreenProps['navigation']>()` reuses the composite type, so a button component inside `Settings` gets `push`, `jumpTo` and root navigation with the same checks as the screen. - `useRoute<RouteProp<ProfileStackParamList, 'NotificationSettings'>>()` types the nested route's params; like any `useRoute` type argument it is an assertion, so use it only in components that render inside that screen. - Registering `RootStackParamList` through the global `ReactNavigation.RootParamList` interface makes plain `useNavigation()` check root-level names and nested `{ screen, params }` objects, although without the navigator-specific methods of any one level. A `reset` into a nested screen is typed through the same `NavigatorScreenParams`, using its `state` form, so a restored or linked state is also checked against the tree. ## Static configuration infers it With the static API, a nested static navigator is used directly as a screen of its parent. `StaticParamList<typeof RootStack>` recognises that and types the parent's param as `NavigatorScreenParams<StaticParamList<typeof MainTabs>> | undefined` automatically, all the way down. Registering the root list once through the global `ReactNavigation.RootParamList` interface then types `useNavigation()` for navigation across the whole tree. | Concern | Dynamic API | Static API | |---|---|---| | Nested params in the parent list | hand-written `NavigatorScreenParams` | inferred by `StaticParamList` | | Props of a nested screen | `CompositeScreenProps` chain | `StaticScreenProps` for params, root-typed hooks | | Risk | chains drift from the real tree | the config is the single source | ## A typed cross-tree call With those types, a single call from `Settings` can target any level and still be checked end to end: ```tsx navigation.navigate('MainTabs', { screen: 'Profile', params: { screen: 'NotificationSettings', params: { section: 'messages' } }, }); ``` Each level of the object is validated against the matching param list: `Profile` must be a tab, `NotificationSettings` must be in the Profile stack, and `section` must be one of its allowed values. **Pitfalls:** - Forgetting `NavigatorScreenParams` and typing `Profile: undefined`: nested navigation then fails to compile, tempting people to cast it away. - Reversing the composite order, which gives the nested screen the parent's route type. - Hand-maintained chains going stale after the tree is restructured; keep the chains next to the navigators and regenerate them together.

  • Why must the screen's own navigator be the first argument of CompositeScreenProps?
    `CompositeScreenProps<A, B>` takes `route` from `A`, and its navigation type takes the route name, state, `setOptions` options and `addListener` events from `A` while combining both param lists. Putting the parent first would type `route`, options and events as the parent's, so the nested screen would read the wrong params.
  • What does typing Profile as NavigatorScreenParams<ProfileStackParamList> | undefined allow that NavigatorScreenParams alone does not?
    The `| undefined` makes the params optional, so `navigate('Profile')` compiles and simply focuses the tab on its current nested screen. Without it, every navigation to `Profile` must name a child screen.

saying these in an interview costs you the question

  • Typing Profile as undefined in the tab list is enough for nested navigation.
  • CompositeScreenProps takes the parent navigator first and the screen's own last.
  • A nested screen's navigation type only needs its own stack's param list.
  • Static configuration cannot type nested navigators, so they need manual casts.