With React Navigation 7, a signed-out patient opens a visit link, signs in, and lands on Home instead; why, and what does UNSTABLE_routeNamesChangeBehavior change?
answer
- VisitRoom undefined while signed out
- default firstMatch forgets the target
- lastUnhandled stores the lost state
- restored only when current state is invalid
- NAVIGATE actions only; core 7.13
basics
~10 sWhile signed out, VisitRoom is not rendered, so the link's state is unhandled and forgotten under the default 'firstMatch'. Setting UNSTABLE_routeNamesChangeBehavior='lastUnhandled' stores that state and restores it once sign-in makes it valid.
solid answer
~40 sWith conditional auth screens, `VisitRoom` does not exist while the patient is signed out. The link produces navigation state pointing at it, the navigator finds no valid route in that state, and initialises with the signed-out branch instead. After sign-in, the default behaviour, `'firstMatch'`, starts the stack on the first signed-in screen: the link's target was never remembered. Since `@react-navigation/core` 7.13, a navigator accepts `UNSTABLE_routeNamesChangeBehavior="lastUnhandled"`: it keeps the last state it could not handle — a link's state, or a `NAVIGATE` to a screen not yet rendered — and when the route names change and the current state has no valid routes left, it restores that state, so the patient lands on `VisitRoom`. It is prefixed `UNSTABLE_`, and the restored params came from a URL, so the screen must still validate them.
code
tsx · 13 lines<Stack.Navigator UNSTABLE_routeNamesChangeBehavior="lastUnhandled">
{isSignedIn ? (
<Stack.Group>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="VisitRoom" component={VisitRoomScreen} />
</Stack.Group>
) : (
<Stack.Group>
<Stack.Screen name="SignIn" component={SignInScreen} />
<Stack.Screen name="SignUp" component={SignUpScreen} />
</Stack.Group>
)}
</Stack.Navigator>go deeper
Know that a screen that is not rendered while signed out cannot be opened by a link, so the link's target is lost unless something remembers it.
Explain the default firstMatch behaviour and what setting the navigator's route-names-change behaviour to lastUnhandled stores and restores.
State the caveats — NAVIGATE only, restore only when the current state is fully invalid, shared screens blocking it — and validate the restored params on the destination.
Decide between an UNSTABLE_ prop and an app-owned pending-link store, weighing upgrade risk, link expiry and auditability for a regulated health app.
## Why the target is lost A telehealth app sends a reminder link that should open a specific video visit. The app uses **conditional auth screens** in **React Navigation 7**, so while the patient is signed out the root stack renders only `SignIn` and `SignUp`; `VisitRoom` is not defined. 1. The link is resolved into a navigation state that targets `VisitRoom` with a `visitId` param. 2. The navigator checks that state against its current route names. None of its routes are valid, so it cannot use it. 3. It initialises with its own initial state instead: the signed-out branch, `SignIn`. 4. The patient signs in. The route names change, the signed-out routes are dropped, and with nothing left the stack starts on `initialRouteName` or the first signed-in screen — `Home`. Nothing in that sequence remembered the link. That default is called **`'firstMatch'`**: when route names change, go to the first matching route of the new list. ## What 'lastUnhandled' changes Since `@react-navigation/core` 7.13.0, a navigator accepts a prop that chooses the behaviour when its route names change: | Value | Behaviour | |---|---| | `'firstMatch'` (default) | Start on the initial or first route of the new list; unhandled state is forgotten | | `'lastUnhandled'` | Remember the last state that could not be handled, and restore it when it becomes valid | With `UNSTABLE_routeNamesChangeBehavior="lastUnhandled"` on the root stack, step 2 above also **stores** the state it could not use. After sign-in, the navigator sees that the stored state's routes are all defined now and that the current state (`SignIn`) has no valid routes left, so it rehydrates the stored state instead of starting on `Home`. The patient lands on `VisitRoom` with its `visitId`. The same mechanism covers in-app navigation: a `NAVIGATE` action to a screen that is not rendered yet (for example a "Join visit" button on a public screen) is stored as unhandled state and replayed after the condition flips. ## Caveats that come straight from the source - **Only `NAVIGATE` actions** are stored for direct navigation; `push`, `replace` and other actions to a missing screen are not. - **Restoration needs the current state to become invalid.** It happens only when none of the current routes is defined any more. If a screen shared by both branches — say `Help` — is focused at the moment of sign-in, the current state stays valid and the stored state is not used. Keying shared screens with a `navigationKey` that changes with auth state avoids that. - **Unhandled state for a different kind of navigator is discarded**, for example a stored tab state reaching a stack. - **The restored stack contains what the stored state described.** Whether `Home` sits below `VisitRoom` depends on how the link was turned into state, which is the linking configuration's job. - **The `UNSTABLE_` prefix** means the API may change in a minor release; pin and re-check on upgrade. ## The alternative without the prop Before this option, apps stored the pending link themselves: capture the URL while signed out, keep it in auth or app state, and after sign-in dispatch a navigation to it from an effect in the signed-in branch. That still works and gives full control (for example expiring a pending link after a few minutes), at the cost of code the prop now provides. ## The security note interviewers like A restored state carries **params that came from outside the app**. `visitId` from a URL is untrusted input: the visit screen must check that the signed-in patient is allowed to join that visit, and must not assume the link was addressed to this user. Deferring a link past sign-in does not validate it. ## What a strong answer shows It explains why the target disappears (the screen was not defined, so the state was unhandled), names the default and the opt-in value, states the two big caveats — `NAVIGATE` only, restored only when the current state is fully invalid — and ends with validating the restored params on the destination screen.
- Why can lastUnhandled fail to restore the link if a Help screen is shared by both branches?The stored state is restored only when the current state has no valid routes. If `Help` is focused at sign-in, its route is still valid, so the navigator keeps the current state and ignores the stored one. Putting shared screens in a group whose `navigationKey` changes with auth state removes their routes and lets the restore happen.
- Does the navigator store a push('VisitRoom') made while signed out?No. For direct navigation, only `NAVIGATE` actions to a screen that is not rendered are stored as unhandled state; other action types to a missing screen are simply unhandled.
saying these in an interview costs you the question
- React Navigation 7 always remembers a deep link and replays it after sign-in by default.
- lastUnhandled restores the link even if a still-valid screen is focused at sign-in.
- Every action type to a missing screen is stored, including push and replace.
- A link restored after sign-in has already been validated, so its params are safe.