In an audiobook app using React Navigation 7 bottom tabs, how do lazy, freezeOnBlur, popToTopOnBlur and detachInactiveScreens change how inactive tabs behave?
answer
- render on first visit, then stay mounted
- freeze re-renders of unfocused tabs
- reset a tab's nested stack on blur
- detach native views to save memory
- all four default to keeping state
basics
~20 slazy (default true) renders a tab on first visit; freezeOnBlur (default false) stops unfocused tabs from re-rendering; popToTopOnBlur (default false) resets a tab's nested stack when leaving it; detachInactiveScreens detaches inactive tabs' native views to save memory.
solid answer
~50 sBottom tab screens stay mounted after their first visit, which preserves state but means an unfocused Library tab still re-renders when a store it subscribes to changes, for example on every playback-progress tick. `lazy` (default `true`) only controls the first render: a tab is rendered when first focused or preloaded, and `false` renders all tabs up front. `freezeOnBlur` (default `false`, or `true` after `enableFreeze()` from `react-native-screens`) suspends re-rendering of unfocused tabs until they are focused again, iOS and Android only. `popToTopOnBlur` (default `false`) pops a stack nested in the tab back to its first screen when the user leaves the tab. `detachInactiveScreens`, a navigator-level prop that in the current source defaults to `true` on iOS, Android and web, detaches the native views of inactive screens to save memory without unmounting their React state. Profile before and after; freezing hides work but does not remove it.
code
tsx · 23 linesimport { 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: {
freezeOnBlur: true,
},
screens: {
Home: HomeScreen,
Search: { screen: SearchScreen, options: { lazy: false } },
Library: { screen: LibraryScreen, options: { popToTopOnBlur: true } },
},
});go deeper
Recall that tabs render on first visit and then stay mounted, which is why their state survives switching.
Explain what each of lazy, freezeOnBlur, popToTopOnBlur and detachInactiveScreens does, with their defaults and the react-native-screens requirement for freezing.
Diagnose background re-renders and memory growth in tabs, apply freezeOnBlur and selective subscriptions, and separate product decisions like popToTopOnBlur from performance fixes.
Decide how the app balances instant tab switching, memory on low-end devices and state retention, and document the defaults every tab should use.
## The default: mounted, visible only when focused A React Navigation 7 bottom tab navigator renders a tab's screen the first time it is focused, then **keeps it mounted**. That is what preserves the Search tab's query and the Library tab's scroll position. The cost is that mounted but unfocused screens are still React components: if the Home tab subscribes to a playback-progress store that updates every second, it keeps re-rendering while the user browses Library. Four options tune this behaviour. | Option | Where | Default | Effect | |---|---|---|---| | `lazy` | Screen option | `true` | Render a tab only when first focused or preloaded | | `freezeOnBlur` | Screen option | `false` (or `true` after `enableFreeze()`) | Suspend re-renders of the tab while unfocused | | `popToTopOnBlur` | Screen option | `false` | Pop a stack nested in the tab back to its first screen when leaving | | `detachInactiveScreens` | Navigator prop | `true` on iOS, Android and web in the current source | Detach inactive screens' native views to save memory | ## `lazy` With the default `lazy: true`, a tab is not rendered until the user first navigates to it, unless it was preloaded. That keeps startup cheap: the Library tab's large grid of covers is not built until needed. `lazy: false` renders the tab on the navigator's first render, which is useful when a tab must be ready instantly and its first render is expensive, at the cost of slower startup. `lazy` has **no effect after the first visit**; it does not unmount anything. ## `freezeOnBlur` `freezeOnBlur` uses `react-native-screens`' freeze support to **suspend re-rendering** of a screen while it is not focused. Updates that would re-render it are held until it becomes focused again, when it catches up. - It defaults to `false`, or to `true` when `enableFreeze()` from `react-native-screens` is called at the top of the app. - It needs `react-native-screens` 3.16 or newer and works only on iOS and Android. - It is the targeted fix for "background tabs re-render on every playback tick". Freezing is documented as suspending re-renders; it is not a way to stop work. Timers, polling and subscriptions that should pause while a tab is hidden belong in focus-aware logic in the screen. ## `popToTopOnBlur` When a tab contains a stack, for example Library, then a book's details, then its chapters, leaving the tab normally preserves that position. `popToTopOnBlur: true` pops the nested stack back to its first screen whenever the user switches away, so returning to Library starts at the library root. It defaults to `false`. Choose it when product wants each tab visit to start fresh; leave it off when users expect to resume where they were. ## `detachInactiveScreens` `detachInactiveScreens` is a navigator-level prop. When `true`, inactive screens are detached from the native view hierarchy to save memory; their React state stays. The option's comment says it defaults to `true` on Android, while the current source defaults it to `true` on web, Android and iOS. Turning it off keeps inactive views attached, which can help when a background tab must keep drawing, at a memory cost. ## A practical tuning order 1. **Measure**: confirm with a profiler that unfocused tabs re-render or that memory grows. 2. **Fix the subscription first**: select only the state the screen needs, so a progress tick does not re-render a whole tab. 3. **Enable `freezeOnBlur`** on heavy tabs that do not need to update while hidden. 4. **Keep `lazy: true`** unless a tab must be instantly ready. 5. **Use `popToTopOnBlur`** only as a product decision about navigation history, not as a performance tool. ## Common misconceptions - **"`lazy` unmounts tabs when you leave them."** It only delays the first render. - **"Frozen tabs are unmounted."** Their state is intact; rendering is paused. - **"Detaching views loses state."** Detachment is a native-view optimisation; React state remains.
- Should you rely on freezeOnBlur to stop polling or a playback-progress timer in a background tab?No. `freezeOnBlur` is documented as suspending re-renders of inactive screens; it is not a mechanism for stopping work. Timers, polling and subscriptions that should run only while a tab is visible need focus-aware logic in the screen itself, which is a separate lifecycle concern.
- When would you set lazy: false on a tab?When a tab must appear instantly on first tap and its first render is expensive, such as a search screen that builds indexes or a heavy list. Rendering it with the navigator trades a slower initial load for no delay on first focus.
saying these in an interview costs you the question
- lazy: true unmounts a tab when the user leaves it.
- freezeOnBlur unmounts the screen and restores it from saved state.
- popToTopOnBlur is a performance option that reduces memory.
- detachInactiveScreens resets the React state of inactive tabs.
- freezeOnBlur is the right way to stop polling and timers in a background tab.