Without root-level state tracking, analytics firing on screen changes, auth-gated redirects, and deep-link-driven UI toggles all become per-screen boilerplate that drifts out of sync. Missing onStateChange means you cannot detect when a user lands on a sensitive screen to refresh a token, or log funnel events centrally. The app ends up with duplicated tracking code and screens that never tell the rest of the app where the user is.
Medium because the app still navigates, but cross-screen analytics and global side effects become unreliable or duplicated.
Attach an onStateChange handler to NavigationContainer so analytics and global reactions fire in one place. Keep the latest state in a ref or store so any screen can read it:
<NavigationContainer
linking={linking}
onStateChange={(state) => {
const route = state?.routes[state.index]
analytics.track('screen_view', { name: route?.name })
}}
>
<RootNavigator />
</NavigationContainer>
Put the handler in src/navigation/index.tsx so it ships with the container.
ID: mobile-navigation-linking.navigation-structure.state-mgmt
Severity: medium
What to look for: Check for root-level state management of navigation. Look for useState or external state libraries (Redux, Zustand) managing navigation state or app-wide conditions. Check whether navigation state is managed in NavigationContainer's onStateChange callback.
Pass criteria: Count all navigation state tracking mechanisms (onStateChange callbacks, state management subscriptions). At least 1 root-level state tracking mechanism must exist via NavigationContainer.onStateChange or a state management library. Any global navigation side effects (analytics, notifications) are handled at root level.
Fail criteria: No state management for navigation. Navigation state is managed only locally in screens without global awareness. No more than 0 global navigation side effects should lack root-level coordination.
Skip (N/A) when: Simple apps with fewer than 3 screens that have no cross-screen state requirements may skip this.
Detail on fail: "No navigation state management found at root level — changes in one screen don't update global state" or "onStateChange callback missing from NavigationContainer"
Remediation: Track navigation state at the root to enable analytics, logging, or state-driven conditionals:
const [navigationState, setNavigationState] = useState()
<NavigationContainer
linking={linking}
onStateChange={state => {
setNavigationState(state)
// Log for analytics, etc.
}}
>
{/* navigators */}
</NavigationContainer>