In React Navigation 7 nested navigators, how does an action from a nested screen reach a parent navigator, and when do you need navigation.getParent()?
answer
- own navigator handles first
- unhandled actions bubble up
- parent methods merged in
- setOptions and events stay local
- getParent(id) with the navigator id
basics
~20 sAn action goes to the screen's own navigator first and bubbles to each parent until one handles it, so navigate('Chat') from a nested screen reaches the root stack. getParent() covers what stays local: the parent screen's options and events.
solid answer
~40 sIn React Navigation 7 a nested screen's `navigation` object dispatches to its own navigator first; if that navigator cannot handle the action, it **bubbles** to the parent, then further up. So from Profile's `Settings` screen, `navigate('Chat', { matchId })` is ignored by the settings stack and the tab navigator and handled by the root stack, and `goBack()` on the stack's first screen falls through to the tab navigator. Parent navigators' action helpers are merged in too, so `jumpTo('Discover')` works from the nested screen. Two things stay local: `setOptions` configures only the screen in its own navigator, and `addListener` receives only that navigator's events. To set a badge on the `Profile` tab or listen for its `tabPress`, call `navigation.getParent()`, or `getParent('MainTabs')` when the tab navigator has `id="MainTabs"`.
code
tsx · 34 linesimport { useEffect } from 'react';
import { useNavigation } from '@react-navigation/native';
import type { BottomTabNavigationProp } from '@react-navigation/bottom-tabs';
import { Button, View } from 'react-native';
// RootStackParamList (MainTabs, Chat, Paywall) is registered globally as ReactNavigation.RootParamList.
type MainTabParamList = { Discover: undefined; Matches: undefined; Profile: undefined };
type MainTabsNavigation = BottomTabNavigationProp<MainTabParamList>;
export function SettingsScreen({ unreadTips }: { unreadTips: number }) {
const navigation = useNavigation();
useEffect(() => {
navigation.getParent<MainTabsNavigation>('MainTabs')?.setOptions({
tabBarBadge: unreadTips > 0 ? unreadTips : undefined,
});
}, [navigation, unreadTips]);
useEffect(() => {
const tabs = navigation.getParent<MainTabsNavigation>('MainTabs');
return tabs?.addListener('tabPress', () => {
console.log('Profile tab pressed while Settings is open');
});
}, [navigation]);
return (
<View>
<Button
title="Open chat"
onPress={() => navigation.navigate('Chat', { matchId: 'm42' })}
/>
</View>
);
}go deeper
Recall that navigating from a nested screen to a parent's screen just works because unhandled actions bubble up to the parent navigators.
Explain the bubbling order, which parent helpers are merged into a nested screen's navigation object, and which APIs stay scoped to its own navigator.
Show you use getParent with navigator ids for tab options and events, keep navigation calls plain where bubbling suffices, and debug unhandled-action errors.
Limit how much nested screens reach into ancestors, preferring shared state or navigator-level options so features do not couple to a particular tree shape.
## One navigation object, several navigators In **React Navigation 7**, each screen receives a `navigation` object built by **its own navigator**. When navigators are nested, that object also carries the parent navigators' helpers. Understanding what is shared and what stays local answers most interview questions about nesting. Use a dating app as the example: ```text RootStack: MainTabs, Chat, Paywall MainTabs (id="MainTabs"): Discover, Matches, Profile Profile: ProfileHome, Settings, NotificationSettings (a stack) ``` ## Actions bubble up When a screen calls a navigation method, the action is offered to navigators in this order: 1. the screen's **own navigator** (here the Profile stack); 2. if it returns no new state, the **parent** (the tab navigator); 3. then the grandparent (the root stack), and so on up to the container. The first navigator that can handle it updates its state, and the navigators above it focus the path to the new screen. Examples from `Settings`: - `navigation.push('NotificationSettings', …)` is handled by the Profile stack itself; - `navigation.navigate('Chat', { matchId: 'm42' })` is not known to the stack or the tabs, so the root stack pushes `Chat` over the whole tab UI; - `navigation.goBack()` on `ProfileHome`, the stack's first screen, cannot pop, so the tab navigator handles it according to its `backBehavior`, which defaults to returning to the first tab. Actions do **not** travel **down** into child navigators by default in v7; reaching a nested child uses the explicit `navigate('Profile', { screen: 'Settings' })` form. If nothing handles an action, development builds log that the action was not handled by any navigator. ## Tracing an action step by step Suppose `NotificationSettings` calls `navigation.navigate('Paywall')` to upsell a premium notification feature: 1. the Profile stack has no `Paywall` route and returns no new state; 2. the tab navigator has no `Paywall` tab and returns no new state; 3. the root stack has `Paywall`, pushes it as a modal and becomes the handler; 4. `Paywall` is focused, and dismissing it pops the root stack, revealing `NotificationSettings` inside the Profile tab unchanged. The same composition shows up in `canGoBack()`: a nested screen reports `true` if its own navigator can go back **or** any parent can, which is why the first screen of a tab's stack can still report that back is possible. ## Parent helpers are merged in The navigation object of a nested screen is built on top of the parent's helpers, so methods that exist only on a parent navigator are callable directly: - `navigation.jumpTo('Discover')`, a tab helper, works from a screen inside the Profile stack; - in a drawer layout, `navigation.openDrawer()` works from screens of a stack nested in the drawer. These dispatch to the navigator that owns the method. ## What stays local Two parts of the navigation object are deliberately scoped to the screen's own navigator: | API | Scope | Consequence in a nested screen | |---|---|---| | `setOptions` | the current route in its own navigator | sets the stack screen's header, not the tab's options | | `addListener` | events emitted for the current route by its own navigator | does not receive the tab navigator's `tabPress` | | `setParams` | the current route | changes the stack route's params, not the tab route's | This is where **`getParent()`** comes in. It returns the navigation object of the screen that renders the current navigator: - `navigation.getParent()?.setOptions({ tabBarBadge: 3 })` from `Settings` sets a badge on the `Profile` tab, because in the parent's navigator the Profile stack is the `Profile` screen; - `navigation.getParent()?.addListener('tabPress', handler)` inside an effect lets a nested screen react to its tab being pressed, returning the unsubscribe function. ## Targeting a specific ancestor `getParent()` without arguments returns the immediate parent. With a string, `getParent('MainTabs')` walks up and returns the navigation object whose navigator has that **`id`** prop, so code keeps working if another navigator is inserted in between. The `id` must be set on the navigator, for example `<Tab.Navigator id="MainTabs">`; asking for an id that no ancestor has returns `undefined`, so the result should be checked. **Common mistakes:** - Calling `navigation.setOptions({ tabBarBadge })` in a nested stack screen and wondering why the tab shows nothing. - Listening for `tabPress` on the nested screen's own `navigation`. - Using `getParent()` to navigate, when a plain `navigate` already bubbles up.
- Why does navigation.setOptions({ tabBarBadge: 3 }) inside the Settings screen not show a badge on the Profile tab?`setOptions` sets options for the current route in its own navigator, the Profile stack, where `tabBarBadge` means nothing. The badge belongs to the `Profile` route of the tab navigator, so call `navigation.getParent()?.setOptions({ tabBarBadge: 3 })`, or `getParent('MainTabs')` if the tab navigator has that id.
- When is getParent('MainTabs') better than getParent()?`getParent()` returns only the immediate parent, so it silently targets the wrong navigator if someone later wraps the stack in another navigator. `getParent('MainTabs')` walks up to the navigator whose `id` prop is `MainTabs`, which keeps the code correct as the tree changes; it returns `undefined` if no ancestor has that id.
saying these in an interview costs you the question
- navigate from a nested screen can only reach screens in its own navigator.
- setOptions in a nested screen changes the parent tab's options too.
- A nested screen's addListener receives the parent tab's tabPress events.
- getParent('MainTabs') matches the route name of the parent screen.
- You need getParent() to navigate to a screen in the root stack.