Nesting navigators
Nesting navigators means rendering a navigator inside a screen of another navigator, for example:
- Static
- Dynamic
const HomeTabs = createBottomTabNavigator({
screens: {
Feed: FeedScreen,
Messages: MessagesScreen,
},
});
const RootStack = createNativeStackNavigator({
screens: {
Home: {
screen: HomeTabs,
options: {
headerShown: false,
},
},
Profile: ProfileScreen,
},
});
function HomeTabs() {
return (
<Tab.Navigator>
<Tab.Screen name="Feed" component={FeedScreen} />
<Tab.Screen name="Messages" component={MessagesScreen} />
</Tab.Navigator>
);
}
function RootStack() {
return (
<Stack.Navigator>
<Stack.Screen
name="Home"
component={HomeTabs}
options={{ headerShown: false }}
/>
<Stack.Screen name="Profile" component={ProfileScreen} />
</Stack.Navigator>
);
}
In this example, a tab navigator (HomeTabs) is nested inside a stack navigator (RootStack) under the Home screen. The structure looks like this:
RootStack(Stack navigator)HomeTabs(Tab navigator)Feed(screen)Messages(screen)
Profile(screen)
Nesting navigators work like nesting regular components. To achieve the behavior you want, it's often necessary to nest multiple navigators.
How nesting navigators affects the behaviour
When nesting navigators, there are some things to keep in mind:
Each navigator keeps its own navigation history
For example, when you press the back button in a screen inside a stack navigator nested within another navigator, it will go to the previous screen of the closest ancestor navigator of the screen. If it's the first screen in the nested stack, pressing back goes to the previous screen in the parent navigator.
Each navigator has its own options
For example, specifying a title option in a screen nested in a child navigator won't affect the title shown in a parent navigator.
If you want to set parent navigator options based on the active screen in a child navigator, see screen options with nested navigators.
Each screen in a navigator has its own params
Any params passed to a screen in a nested navigator are in the route object of that screen and aren't accessible from a screen in a parent or child navigator.
If you need to access params of the parent screen from a child screen, you can use React Context to expose params to children.
Navigation actions are handled by current navigator and bubble up if couldn't be handled
Navigation actions first go to the current navigator. If it can't handle them, they bubble up to the parent. For example, calling goBack() in a nested screen goes back in the nested navigator first, then the parent if already on the first screen.
Navigator specific methods are available in the navigators nested inside
If you have a stack inside a drawer navigator, the drawer's openDrawer, closeDrawer, toggleDrawer methods etc. are available on the navigation object in the screens inside the stack navigator. Similarly, if you have a tab navigator inside stack navigator, the screens in the tab navigator will get the push and replace methods for stack in their navigation object.
If you need to dispatch actions to the nested child navigators from a parent, you can use navigation.dispatch:
navigation.dispatch(DrawerActions.toggleDrawer());
Nested navigators don't receive parent's events
Screens in a nested navigator don't receive events from the parent navigator (like tabPress). To listen to parent's events, use navigation.getParent:
- Static
- Dynamic
const unsubscribe = navigation
.getParent('MyTabs')
.addListener('tabPress', (e) => {
// Do something
alert('Tab pressed!');
});
const unsubscribe = navigation
.getParent('MyTabs')
.addListener('tabPress', (e) => {
// Do something
alert('Tab pressed!');
});
Here 'MyTabs' is the id of the parent navigator whose events you want to listen to.
Parent navigator's UI is rendered on top of child navigator
The parent navigator's UI renders on top of the child. For example, a drawer nested inside a stack appears below the stack's header, while a stack nested inside a drawer appears below the drawer.
Common patterns:
- Tab navigator nested inside the initial screen of stack navigator - New screens cover the tab bar when you push them.
- Drawer navigator nested inside the initial screen of stack navigator with the initial screen's stack header hidden - The drawer can only be opened from the first screen of the stack.
- Stack navigators nested inside each screen of drawer navigator - The drawer appears over the header from the stack.
- Stack navigators nested inside each screen of tab navigator - The tab bar is always visible. Usually pressing the tab again also pops the stack to top.
Navigating to a screen in a nested navigator
Consider the following example:
- Static
- Dynamic
const MoreTabs = createBottomTabNavigator({
screens: {
Feed: FeedScreen,
Messages: MessagesScreen,
},
});
const RootStack = createNativeStackNavigator({
screens: {
Home: HomeScreen,
More: {
screen: MoreTabs,
options: {
headerShown: false,
},
},
},
});
function MoreTabs() {
return (
<Tab.Navigator>
<Tab.Screen name="Feed" component={FeedScreen} />
<Tab.Screen name="Messages" component={MessagesScreen} />
</Tab.Navigator>
);
}
function RootStack() {
return (
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen
name="More"
component={MoreTabs}
options={{ headerShown: false }}
/>
</Stack.Navigator>
);
}
To navigate to the More screen (which contains MoreTabs) from your HomeScreen:
navigation.navigate('More');
This shows the initial screen inside MoreTabs (in this case, Feed). To navigate to a specific screen inside the nested navigator, pass the screen name in params:
navigation.navigate('More', { screen: 'Messages' });
Now Messages will be shown instead of Feed.
Passing params to a screen in a nested navigator
You can also pass params by specifying a params key:
- Static
- Dynamic
navigation.navigate('More', {
screen: 'Messages',
params: { user: 'jane' },
})
navigation.navigate('More', {
screen: 'Messages',
params: { user: 'jane' },
})
If the navigator was already rendered, navigating to another screen will push a new screen in case of stack navigator.
You can follow a similar approach for deeply nested screens. Note that the second argument to navigate here is just params, so you can do something like:
navigation.navigate('Home', {
screen: 'Settings',
params: {
screen: 'Sound',
params: {
screen: 'Media',
},
},
});
In the above case, you're navigating to the Media screen, which is in a navigator nested inside the Sound screen, which is in a navigator nested inside the Settings screen.
The screen and related params are reserved for internal use and are managed by React Navigation. While you can access route.params.screen etc. in the parent screens, relying on them may lead to unexpected behavior.
Rendering initial route defined in the navigator
By default, when you navigate a screen in the nested navigator, the specified screen is used as the initial screen and the initialRouteName prop on the navigator is ignored.
If you need to render the initial route specified in the navigator, you can disable the behaviour of using the specified screen as the initial screen by setting initial: false:
navigation.navigate('Root', {
screen: 'Settings',
initial: false,
});
This affects what happens when pressing the back button. When there's an initial screen, the back button will take the user there.
Avoiding multiple headers when nesting
When nesting navigators, you may see headers from both parent and child. To show only the child navigator's header, set headerShown: false on the parent screen:
- Static
- Dynamic
const HomeTabs = createBottomTabNavigator({
screens: {
Feed: FeedScreen,
Messages: MessagesScreen,
},
});
const RootStack = createNativeStackNavigator({
screens: {
Home: {
screen: HomeTabs,
options: {
headerShown: false,
},
},
Profile: ProfileScreen,
},
});
function HomeTabs() {
return (
<Tab.Navigator>
<Tab.Screen name="Feed" component={FeedScreen} />
<Tab.Screen name="Messages" component={MessagesScreen} />
</Tab.Navigator>
);
}
function RootStack() {
return (
<Stack.Navigator>
<Stack.Screen
name="Home"
component={HomeTabs}
options={{
headerShown: false,
}}
/>
<Stack.Screen name="Profile" component={ProfileScreen} />
</Stack.Navigator>
);
}
This applies regardless of the nesting structure. If you don't want headers in any of the navigators, specify headerShown: false in all of them:
- Static
- Dynamic
const HomeTabs = createBottomTabNavigator({
screenOptions: {
headerShown: false,
},
screens: {
Feed: FeedScreen,
Messages: MessagesScreen,
},
});
const RootStack = createStackNavigator({
screenOptions: {
headerShown: false,
},
screens: {
Home: HomeTabs,
Profile: ProfileScreen,
},
});
function HomeTabs() {
return (
<Tab.Navigator
screenOptions={{
headerShown: false,
}}
>
<Tab.Screen name="Feed" component={FeedScreen} />
<Tab.Screen name="Messages" component={MessagesScreen} />
</Tab.Navigator>
);
}
function RootStack() {
return (
<Stack.Navigator
screenOptions={{
headerShown: false,
}}
>
<Stack.Screen name="Home" component={HomeTabs} />
<Stack.Screen name="Profile" component={ProfileScreen} />
</Stack.Navigator>
);
}
Best practices when nesting
Minimize nesting as much as possible. Nesting has many downsides:
- Deeply nested view hierarchy can cause memory and performance issues on lower end devices
- Nesting the same type of navigator (e.g. tabs inside tabs, drawer inside drawer etc.) leads to a confusing UX
- With excessive nesting, code becomes difficult to follow when navigating to nested screens, configuring deep links etc.
Think of nesting as a way to achieve the UI you want, not a way to organize your code. To group screens for organization, use the Group component for dynamic configuration or groups property for static configuration.
- Static
- Dynamic
const MyStack = createStackNavigator({
screens: {
// Common screens
},
groups: {
// Common modal screens
Modal: {
screenOptions: {
presentation: 'modal',
},
screens: {
Help,
Invite,
},
},
// Screens for logged in users
User: {
if: useIsLoggedIn,
screens: {
Home,
Profile,
},
},
// Auth screens
Guest: {
if: useIsGuest,
screens: {
SignIn,
SignUp,
},
},
},
});
<Stack.Navigator>
{isLoggedIn ? (
// Screens for logged in users
<Stack.Group>
<Stack.Screen name="Home" component={Home} />
<Stack.Screen name="Profile" component={Profile} />
</Stack.Group>
) : (
// Auth screens
<Stack.Group screenOptions={{ headerShown: false }}>
<Stack.Screen name="SignIn" component={SignIn} />
<Stack.Screen name="SignUp" component={SignUp} />
</Stack.Group>
)}
{/* Common modal screens */}
<Stack.Group screenOptions={{ presentation: 'modal' }}>
<Stack.Screen name="Help" component={Help} />
<Stack.Screen name="Invite" component={Invite} />
</Stack.Group>
</Stack.Navigator>