skip to content

In React Navigation 7, how do a navigator's screenOptions, a group's screenOptions, a screen's options and navigation.setOptions combine for one screen?

level: middleimportance: should knowfreq 45%

answer

  1. merged in a fixed order
  2. later sources win per key
  3. functions receive route, navigation, theme
  4. headerShown defaults to true
  5. setOptions is the last word

basics

~20 s

They are merged per key in order: the navigator's screenOptions, then each group's screenOptions, then the screen's options, then anything set with navigation.setOptions. Later sources win, and any of them can be a function of { route, navigation, theme }.

solid answer

~50 s

React Navigation builds each screen's options by merging, key by key, four sources in a fixed order: the navigator's `screenOptions`, the `screenOptions` of any enclosing `Group` (or static `groups` entry), the screen's own `options`, and finally values set at runtime with `navigation.setOptions`. A later source overrides an earlier one only for the keys it sets. Each of the first three can be an object or a function receiving `{ route, navigation, theme }`, which is how an audiobook app's tab navigator picks `tabBarIcon` per `route.name` in one place. `headerShown` defaults to `true` in the stack, tab and drawer navigators, so a screen that draws its own header sets `headerShown: false`, often via the navigator's `screenOptions` with a per-screen exception. Use `setOptions` for values only known after the screen renders, such as the loaded book title.

code

tsx · 26 lines
tsx
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { Text } from 'react-native';

function HomeScreen() {
  return <Text>Continue listening</Text>;
}
function SearchScreen() {
  return <Text>Search</Text>;
}
function LibraryScreen() {
  return <Text>Library</Text>;
}

export const RootTabs = createBottomTabNavigator({
  screenOptions: ({ route, theme }) => ({
    tabBarActiveTintColor: theme.colors.primary,
    tabBarIcon: ({ color, size }) => (
      <Text style={{ color, fontSize: size * 0.6 }}>{route.name.slice(0, 1)}</Text>
    ),
  }),
  screens: {
    Home: HomeScreen,
    Search: { screen: SearchScreen, options: { headerShown: false } },
    Library: { screen: LibraryScreen, options: { tabBarBadge: 3 } },
  },
});

go deeper

for a junior

Recall that screenOptions sets defaults for every screen and a screen's options override them, and that headerShown: false hides the header.

for a middle

Explain the full merge order including groups and setOptions, the per-key shallow merge, and options functions that receive route, navigation and theme.

for a senior

Structure shared options so screens stay simple, spot deep-merge assumptions in reviews, and use setOptions only for runtime-dependent values.

for a principal

Establish where navigation chrome is configured across a large app so theming and header behaviour stay consistent as teams add screens.

## Where a screen's options come from Options configure what a navigator draws around a screen: the header title, `headerShown`, tab bar icons, `presentation`, gestures. For any screen, React Navigation computes the final options object from four sources, merged in this order: 1. The navigator's **`screenOptions`**. 2. The **`screenOptions` of each `Group`** the screen belongs to (`groups` in the static API). 3. The screen's own **`options`**. 4. Values set at runtime with **`navigation.setOptions(...)`** from inside the screen. The merge is a shallow `Object.assign` in that order, so **a later source wins for the keys it sets** and leaves the others alone. | Source | Scope | Typical use | |---|---|---| | Navigator `screenOptions` | Every screen in the navigator | Shared tab icon logic, header colours, `headerShown` default | | Group `screenOptions` | Screens in the group | A set of modal screens with the same presentation | | Screen `options` | One screen | Its title, an exception to the shared defaults | | `navigation.setOptions` | One mounted screen, at runtime | A title from loaded data, a header button bound to state | ## Options as functions `screenOptions`, group `screenOptions` and `options` can each be a function. It receives **`{ route, navigation, theme }`** and returns an options object. That makes per-route logic possible in one place, for example: - choosing a tab icon from `route.name` in the tab navigator's `screenOptions`; - deriving a title from `route.params`; - reading colours from the active `theme`. The function runs for each screen when options are computed, so keep it cheap. ## Group-level options Groups exist mostly to share options without repeating them: - **Dynamic API**: `<Stack.Group screenOptions={{ presentation: 'modal' }}>` wraps the modal screens. - **Static API**: `groups: { Modals: { screenOptions: { presentation: 'modal' }, screens: { Player, SleepTimer } } }`. A group's `screenOptions` sit between the navigator's and each screen's own `options` in the merge order, so a single screen inside the group can still override them. ## `headerShown` `headerShown` controls whether the navigator draws its header for a screen. It defaults to `true` in the native stack, JS stack, bottom tabs and drawer. Common patterns: - **Hide it everywhere, show it on some screens**: `screenOptions: { headerShown: false }` on the navigator, `options: { headerShown: true }` on the exceptions. - **A screen that draws its own immersive header**, such as a full-bleed audiobook cover on the Player screen, sets `headerShown: false` in its own options. When navigators are combined, hiding one of two headers is a question about nesting, which has its own rules. ## `navigation.setOptions` `setOptions` is the last source and therefore always wins. Use it for values that depend on what the screen has loaded or on its state: - setting the header title to the book's name once its metadata arrives; - adding a `headerRight` button whose `onPress` uses state inside the screen. Call it in an effect, not during render, and remember that it only affects the screen whose `navigation` object you call it on. ## Worked example: an audiobook app's tabs For the Home, Search and Library tabs: - The tab navigator's `screenOptions` is a function returning `tabBarIcon` based on `route.name` and `tabBarActiveTintColor` from the theme. - The Library screen's `options` add `tabBarBadge` with the number of new downloads. - The Search screen's `options` set `headerShown: false` because it renders its own search field at the top. Because merging is per key, the Library screen keeps the shared icon while adding its badge, and the Search screen keeps its icon while hiding its header. ## Pitfalls - **Expecting deep merges.** Nested values like `headerStyle` or `tabBarStyle` are replaced, not merged: setting `headerStyle: { backgroundColor }` on a screen replaces the navigator's whole `headerStyle` object. - **Setting options in render.** `setOptions` during render causes needless updates; use an effect. - **Putting a screen-specific rule in `screenOptions`.** It works, but a `route.name` switch that keeps growing is usually a sign that the rule belongs in that screen's `options`.

  • A screen sets headerStyle: { backgroundColor: 'black' } and loses the navigator's headerStyle shadow settings. Why?
    Options are merged shallowly, key by key. `headerStyle` is one key, so the screen's object replaces the navigator's object entirely instead of merging into it. Repeat the shared properties in the screen's value, or compute both from a shared helper.
  • When is navigation.setOptions the right tool instead of the screen's options?
    When the value is only known inside the mounted screen: a title from fetched book metadata, or a header button whose handler uses the screen's state. Call it from an effect; it is merged last, so it overrides every static source for the keys it sets.

saying these in an interview costs you the question

  • A screen's options are ignored whenever the navigator sets screenOptions.
  • Options objects are deep-merged, so nested styles combine.
  • navigation.setOptions only works before the screen first renders.
  • headerShown defaults to false in bottom tabs.
  • screenOptions functions receive only the route, not the theme.