With React Navigation 7's static configuration, how do the if hooks on screens and groups express a signed-in and signed-out split?
answer
- if takes a hook, not a flag
- useIsSignedIn / useIsSignedOut pair
- group key becomes navigationKey
- provider wraps the Navigation component
- same router rules as dynamic
basics
~20 sIn React Navigation 7's static API, a screen or group's if property takes a hook returning a boolean; the entry renders only while it returns true, so signed-in and signed-out groups swap exactly like conditionally rendered dynamic screens.
solid answer
~50 sIn the static API the screen list is an object, so conditions cannot be JSX. Instead each screen or entry under `groups` can have an `if` property holding a **hook** — for example `if: useIsSignedIn` on a `SignedIn` group and `if: useIsSignedOut` on a `SignedOut` group. React Navigation calls these hooks while rendering the navigator and renders the entry only when the hook returns `true`. Because they are hooks, they read auth state from a context or store, which means the `Navigation` component from `createStaticNavigation` must render inside that provider. The router rules are unchanged: when the result flips, routes for screens that stopped rendering are dropped. Each static group is rendered with its object key as its `navigationKey`, and the splash while restoring is usually handled by not rendering `Navigation` until the state is known.
code
tsx · 34 linesimport * as React from 'react';
import { createStaticNavigation } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { AuthContext } from './AuthContext';
import { HomeScreen, SignInScreen, SignUpScreen, VisitRoomScreen } from './screens';
function useIsSignedIn() {
return React.useContext(AuthContext).token != null;
}
function useIsSignedOut() {
return !useIsSignedIn();
}
const RootStack = createNativeStackNavigator({
groups: {
SignedIn: {
if: useIsSignedIn,
screens: {
Home: HomeScreen,
VisitRoom: VisitRoomScreen,
},
},
SignedOut: {
if: useIsSignedOut,
screens: {
SignIn: SignInScreen,
SignUp: SignUpScreen,
},
},
},
});
export const Navigation = createStaticNavigation(RootStack);go deeper
Recall that static configs gate screens with an if property whose value is a hook returning a boolean, usually a signed-in and a signed-out group.
Explain why if must be a hook, where the auth provider must sit relative to createStaticNavigation's component, and that router behaviour matches the dynamic API.
Note that each static group's key becomes its navigationKey, the implications for shared screens, and that inferred types still include screens an if hook hides.
Judge when the static API's generated linking and types outweigh the flexibility of dynamic JSX conditions for a large app's auth and feature-flagged screens.
## Two ways to declare navigators **React Navigation 7** offers two APIs for the same navigators: - The **dynamic API**: `<Stack.Navigator>` with `<Stack.Screen>` children in JSX, where an auth flow is an ordinary ternary around two branches. - The **static API**: `createNativeStackNavigator({ screens, groups })`, an object turned into a component with `createStaticNavigation(RootStack)`. It generates linking config and type information from the object, but an object literal has no place for a JSX conditional. The static API solves that with an **`if`** property on each screen entry and on each entry under `groups`. ## How `if` works The value of `if` is a function that React Navigation calls **as a hook** while rendering the navigator, and it must return a boolean: 1. If it returns `true`, the screen or group is rendered. 2. If it returns `false`, it is not rendered, so from the router's point of view those screens do not exist. 3. When the returned value changes, the navigator re-renders with a different list of route names, and the same router rules as the dynamic API apply: routes for screens that stopped rendering are dropped, and an empty stack starts on `initialRouteName` (when rendered) or the first screen. Because it is a hook, it can use `React.useContext` or a store's hook. The usual pair is: ```tsx function useIsSignedIn() { return React.useContext(AuthContext).token != null; } function useIsSignedOut() { return !useIsSignedIn(); } ``` React Navigation's source calls the hooks of every screen in a group before evaluating the group's own `if`, so the number of hooks called per render stays stable even when a group is hidden. The rules of hooks still apply to what you write *inside* your `if` hook: no conditional hook calls. ## Groups and navigationKey | Static feature | Dynamic equivalent | |---|---| | `groups: { SignedIn: { if: useIsSignedIn, screens: {...} } }` | `{isSignedIn && <Stack.Group>...</Stack.Group>}` | | `screens: { Help: { screen: HelpScreen, if: useIsHelpEnabled } }` | `{isHelpEnabled && <Stack.Screen name="Help" ... />}` | | A group's object key (`SignedIn`) | `<Stack.Group navigationKey="SignedIn">` | | `createStaticNavigation(RootStack)` | `<NavigationContainer>` around the navigator | The third row matters: each static group is rendered with its object key passed as the group's **`navigationKey`**. That key is constant for a given group, so it resets nothing by itself; what makes a static auth flow clear history is that a whole group stops rendering. A screen shared by both states, placed in an unconditional group, survives the flip exactly as it would in the dynamic API. ## Where the auth state must live The `if` hooks run inside the navigator, so whatever they read must be provided **above** the component returned by `createStaticNavigation`: - Wrap `<Navigation />` in the auth context provider, or use a store hook that needs no provider. - The sign-in screen updates that same state; it never calls `navigate`. - The splash while restoring a saved token is usually a check *before* `<Navigation />` is rendered — return a splash view while the state is still `restoring` — or a group whose `if` is a `useIsRestoring` hook. ## Typing The static API infers the param list from the whole config object, so a screen appears in the types even while its `if` hides it. Types describe every screen that can exist, not the ones rendered right now; at runtime, navigating to a hidden screen is still an unhandled action. How to register and consume those inferred types is a separate topic. ## Common mistakes - Passing a value instead of a hook, so the condition is frozen at module load. - Rendering `<Navigation />` outside the auth provider, so the hooks read a default context value. - Calling `navigate` after sign-in, which the static API makes no more necessary than the dynamic one. ## What a strong answer shows A good answer states that `if` takes a hook rather than a boolean, pairs a signed-in and a signed-out group, notes that the provider must wrap the static `Navigation` component, and explains that nothing about history changes: the router's behaviour when the rendered screens change is the same in both APIs.
- Why must the if value be a hook rather than a module-level boolean?The static config object is created once, at module load, before any auth state exists. A hook is called on every render of the navigator, so it can read the current value from context or a store and make the navigator re-render when that value changes. A plain boolean captured at module load would never change.
- Does a hidden screen disappear from the static API's inferred types?No. Types are inferred from the whole configuration, so a screen gated by `if` is still in the param list. At runtime, navigating to it while its hook returns false is an unhandled action, exactly as with a dynamic screen that is not rendered.
saying these in an interview costs you the question
- The if property takes a boolean such as isSignedIn read when the config is created.
- The static API cannot express auth flows, so auth apps must use the dynamic API.
- An async function in if can wait for the stored token before rendering.
- Screens hidden by if stay in history until the user presses back.