skip to content

With React Navigation 7's static configuration, how do the if hooks on screens and groups express a signed-in and signed-out split?

level: middleimportance: should knowfreq 24%

answer

  1. if takes a hook, not a flag
  2. useIsSignedIn / useIsSignedOut pair
  3. group key becomes navigationKey
  4. provider wraps the Navigation component
  5. same router rules as dynamic

basics

~20 s

In 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 s

In 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 lines
tsx
import * 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

for a junior

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.

for a middle

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.

for a senior

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.

for a principal

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.