A screen reads and reacts to navigation state through five small primitives: the navigator
handle, the current route, whether the screen is focused right now, an effect that re-runs on
focus/blur, and a live selector over the navigator’s state. Every adapter names them per its own
framework idiom — React, Vue, and Svelte use useX(), Angular uses injectX(). That’s not a
typo: Angular’s own ecosystem convention names an inject() -based
function injectX, the same way this site’s Angular guide documents for
every other Angular-adapter API. Svelte calls this bucket “runes” rather than “hooks”, but the
function names are identical to React’s/Vue’s. Solid splits the bucket by what a primitive does,
not by a blanket prefix: useX() for the ones that only read an existing scope (useNavigation/
useRoute and the three narrowed variants, Solid’s own useContext sense), and createX() for
the ones that own a signal or a subscription (createIsFocused/createFocusEffect/
createNavigationState, Solid’s createSignal sense). All five read from the nearest enclosing
<Stack>, <Tab>, or <Drawer> screen and throw if called outside one.
Tip
This is the primary way to read route/navigation in any component under a navigator —
the screen’s own top-level component included, not only ones nested below it (see
Core concepts ). Call useRoute()/useNavigation() (or
Angular’s injectRoute()/injectNavigation()), or the navigator-specific narrowed variants
below. The other three primitives on this page — useIsFocused(), useFocusEffect(), and
useNavigationState() — are read the same way and have no other API, at any nesting depth
(Solid: createIsFocused()/createFocusEffect()/createNavigationState()).
Svelte’s runes return a boxed getter ({ readonly current: T }), read as .current inside
a $derived/template/$effect — the same unwrap pattern as Vue’s ComputedRef.value. Solid’s
primitives return a plain Accessor<T> (a () => T function), read by calling it, e.g.
route(), inside a createMemo/JSX expression/createEffect.
useRoute/injectRoute returns the current screen’s route (name, key, params); useNavigation/
injectNavigation returns the imperative handle for whichever navigator mounted the screen
(push/pop/… for a Stack screen, jumpTo for a Tab screen, openDrawer/… for a
Drawer screen). Each framework hands back a different shape , matching how that framework
tracks changing values:
Primitive
Signature
Navigator handle
useNavigation(): INavigationHandle — plain object, re-read every render
Current route
useRoute(): IRoute<unknown>
Primitive
Signature
Navigator handle
useNavigation(): ComputedRef<INavigationHandle> — read .value
Current route
useRoute(): ComputedRef<IRoute<unknown>>
Primitive
Signature
Navigator handle
injectNavigation(): INavigationHandle — plain object, read once at injection time
Current route
injectRoute(): Signal<IRoute<unknown>> — call as route()
Primitive
Signature
Navigator handle
useNavigation(): { readonly current: INavigationHandle } — boxed getter, read .current
Current route
useRoute(): { readonly current: IRoute<unknown> } — boxed getter, read .current
Primitive
Signature
Navigator handle
useNavigation(): Accessor<INavigationHandle> - call to read
Current route
useRoute(): Accessor<IRoute<unknown>> - call to read
Note
useNavigation/injectNavigation returns IAnyNavigatorHandle & { addListener, getParent },
where IAnyNavigatorHandle is a union of INavigatorHandle | ITabNavigatorHandle | IDrawerNavigatorHandle — the hook itself doesn’t know which of the three navigator kinds
mounted the calling component. Since a nested child’s enclosing navigator kind never changes at
runtime, that’s almost always known upfront — so reach for useStackNavigation()/
useTabNavigation()/useDrawerNavigation() (or Angular’s injectStackNavigation()/
injectTabNavigation()/injectDrawerNavigation()) instead of useNavigation(): each is
useNavigation() plus the narrowing check done once, inside the library, so the call site gets
a concretely-typed handle back with nothing to check — and a clear thrown error if it turns out
to be nested under the wrong kind.
import { useStackNavigation, useRoute } from ' @symbiote-native/navigation/react ' ;
function ProfileScreen () {
const navigation = useStackNavigation ();
const route = useRoute ();
title = { ` Push detail for ${ route . name } ` }
onPress = { () => navigation . push ( ' Detail ' , { id: route . params } ) }
import { useStackNavigation, useRoute } from ' @symbiote-native/navigation/vue ' ;
const navigation = useStackNavigation ();
const route = useRoute ();
navigation . value . push ( ' Detail ' , { id: route . value . params });
< ActionButton :title = " `Push detail for ${route.value.name}` " @press = " pushDetail " />
import { Component } from ' @angular/core ' ;
import { ActionButton } from ' ../components/ActionButton ' ;
import { injectStackNavigation, injectRoute } from ' @symbiote-native/navigation/angular ' ;
<ActionButton [title]="'Push detail for ' + route().name" (press)="pushDetail()"></ActionButton>
export class ProfileScreen {
private readonly navigation = injectStackNavigation ();
readonly route = injectRoute ();
this . navigation . push ( ' Detail ' , { id: this . route () . params });
import { useStackNavigation, useRoute } from ' @symbiote-native/navigation/svelte ' ;
import ActionButton from ' ../components/ActionButton.svelte ' ;
const navigation = useStackNavigation ();
const route = useRoute ();
title = { ` Push detail for ${ route . current . name } ` }
onPress = { () => navigation . current . push ( ' Detail ' , { id: route . current . params } ) }
import { useStackNavigation, useRoute } from ' @symbiote-native/navigation/solid ' ;
import { ActionButton } from ' ../components/ActionButton ' ;
function ProfileScreen () {
const navigation = useStackNavigation ();
const route = useRoute ();
title = { ` Push detail for ${ route () . name } ` }
onPress = { () => navigation () . push ( ' Detail ' , { id: route () . params } ) }
useIsFocused/injectIsFocused reports whether the screen is focused right now.
useFocusEffect/injectFocusEffect runs an effect while the screen is focused and runs its own
returned cleanup on blur — the same shape as a plain effect, just re-armed on every focus/blur
pair instead of once on mount.
Caution
React’s useFocusEffect must be given a memoized callback (wrap it in
useCallback) — a new function identity on every render re-subscribes the
effect, same as any other useEffect dependency. Vue’s useFocusEffect,
Angular’s injectFocusEffect, Svelte’s useFocusEffect, and Solid’s
createFocusEffect need no memoization: setup(), a component constructor, a
Svelte component’s script, and a Solid component body each run exactly once,
so the callback is captured a single time by value.
useIsFocused/injectIsFocused always starts false . It never guesses focus from stack
position — it only flips true once the route’s real focus event fires, which can lag one
microtask behind mount. That’s intentional: it matches the async timing of a real native screen
transition, not an instant JS-only mount.
Primitive
Signature
Is focused
useIsFocused(): boolean
Focus effect
useFocusEffect(effect: EffectCallback): void
Primitive
Signature
Is focused
useIsFocused(): Ref<boolean> — read .value
Focus effect
useFocusEffect(effect: () => (() => void) | void): void
Primitive
Signature
Is focused
injectIsFocused(): Signal<boolean> — call as isFocused()
Focus effect
injectFocusEffect(effect: () => (() => void) | void): void
Primitive
Signature
Is focused
useIsFocused(): { readonly current: boolean } — boxed getter, read .current
Focus effect
useFocusEffect(effect: () => (() => void) | void): void
Primitive
Signature
Is focused
createIsFocused(): Accessor<boolean> - call to read
Focus effect
createFocusEffect(effect: () => (() => void) | void): void
import { useCallback, useState } from ' react ' ;
import { useFocusEffect } from ' @symbiote-native/navigation/react ' ;
function HooksDemoScreen () {
const [ focusCount , setFocusCount ] = useState ( 0 );
setFocusCount ( count => count + 1 );
return < text > { ` focus count: ${ focusCount } ` } </ text > ;
import { ref } from ' vue ' ;
import { useFocusEffect } from ' @symbiote-native/navigation/vue ' ;
const focusCount = ref ( 0 );
< text > {{ `focus count: ${focusCount}` }} </ text >
import { Component, signal } from ' @angular/core ' ;
import { SYMBIOTE_ELEMENTS } from ' @symbiote-native/angular ' ;
import { injectFocusEffect } from ' @symbiote-native/navigation/angular ' ;
imports: [ SYMBIOTE_ELEMENTS ],
template: ` <text>{{ 'focus count: ' + focusCount() }}</text> ` ,
export class HooksDemoScreen {
readonly focusCount = signal ( 0 );
injectFocusEffect ( () => {
this . focusCount . update ( count => count + 1 );
import { useFocusEffect } from ' @symbiote-native/navigation/svelte ' ;
let focusCount = $ state ( 0 );
< text > { ` focus count: ${ focusCount } ` } </ text >
import { createSignal } from ' solid-js ' ;
import { createFocusEffect } from ' @symbiote-native/navigation/solid ' ;
function HooksDemoScreen () {
const [ focusCount , setFocusCount ] = createSignal ( 0 );
createFocusEffect ( () => {
setFocusCount ( count => count + 1 );
return < text > { ` focus count: ${ focusCount () } ` } </ text > ;
useNavigationState/injectNavigationState takes a selector over the navigator’s full
INavigatorState and returns (a wrapper over) the selected value, updating whenever the
navigator’s state changes.
Primitive
Signature
Navigator state
useNavigationState<T>(selector: (state) => T): T
Primitive
Signature
Navigator state
useNavigationState<T>(selector): ShallowRef<T>
Primitive
Signature
Navigator state
injectNavigationState<T>(selector): Signal<T>
Primitive
Signature
Navigator state
useNavigationState<T>(selector): { readonly current: T } — boxed getter, read .current
Primitive
Signature
Navigator state
createNavigationState<T>(selector): Accessor<T> - call to read
Note
Only <Stack> currently broadcasts live state updates. Under <Tab> or <Drawer>, this
hook/composable/injector/rune silently stays on its initial one-route snapshot — it never
re-fires after the first render, because Tab/Drawer don’t emit the state event Stack
does. This is a real, documented scoping limitation of the current implementation, not a bug to
route around.
import { useNavigationState } from ' @symbiote-native/navigation/react ' ;
function RouteStackList () {
const routeNames = useNavigationState ( state => state . routes . map ( route => route . name ));
return routeNames . map ( ( name , index ) => < text key = { name } > { ` ${ index } . ${ name } ` } </ text > );
import { useNavigationState } from ' @symbiote-native/navigation/vue ' ;
const routeNames = useNavigationState ( state => state . routes . map ( route => route . name ));
< text v-for = " (name, index) in routeNames " :key = " name " > {{ `${index}. ${name}` }} </ text >
import { Component } from ' @angular/core ' ;
import { SYMBIOTE_ELEMENTS } from ' @symbiote-native/angular ' ;
import { injectNavigationState } from ' @symbiote-native/navigation/angular ' ;
imports: [ SYMBIOTE_ELEMENTS ],
@for (name of routeNames(); track name + '-' + $index; let index = $index) {
<text>{{ index + '. ' + name }}</text>
export class RouteStackList {
readonly routeNames = injectNavigationState ( state => state . routes . map ( route => route . name ));
import { useNavigationState } from ' @symbiote-native/navigation/svelte ' ;
const routeNames = useNavigationState ( state => state . routes . map ( route => route . name ));
{# each routeNames . current as name, index ( ` ${ name } - ${ index } ` )} < text > { ` ${ index } . ${ name } ` } </ text > {/ each }
import { For } from ' solid-js ' ;
import { createNavigationState } from ' @symbiote-native/navigation/solid ' ;
function RouteStackList () {
const routeNames = createNavigationState ( state => state . routes . map ( route => route . name ));
< For each = { routeNames () } >
{ ( name , index ) => < text > { ` ${ index () } . ${ name } ` } </ text > }
getParent() walks exactly one hop up to the enclosing navigator when navigators are nested —
for example a Tab navigator mounted as the content of a Stack screen. There’s no multi-hop or
named-ancestor lookup; a screen two levels deep needs to call getParent() on the handle
getParent() already returned.
import { isStackNavigatorHandle } from ' @symbiote-native/navigation ' ;
import { useTabNavigation } from ' @symbiote-native/navigation/react ' ;
function NestedTabHomeScreen () {
const navigation = useTabNavigation ();
const parent = navigation . getParent ();
if (parent !== undefined && isStackNavigatorHandle (parent))
navigation here is the Tab screen’s own handle (jumpTo/setParams, already concretely
typed via useTabNavigation()); getParent() still returns the IAnyNavigatorHandle union
though, since — unlike the navigator that mounted this screen — it doesn’t statically know what
kind of navigator is above it. This is the one place a raw guard is genuinely unavoidable, so
isStackNavigatorHandle()/isTabNavigatorHandle()/isDrawerNavigatorHandle() live in the
framework-agnostic core and are importable from the bare @symbiote-native/navigation package
(the same place IRoute/INavigatorState come from), not from /react//vue//angular//svelte.
Vue’s equivalent reads navigation.value.getParent(); Svelte’s reads
navigation.current.getParent(); Angular’s reads injectTabNavigation().getParent(); Solid’s
reads navigation().getParent() - same one-hop semantics in all four.
Signature
Description
useNavigation(): INavigationHandle
The current screen’s navigator handle (IAnyNavigatorHandle union) plus addListener and getParent
useStackNavigation(): IStackNavigationHandle
useNavigation() narrowed to a Stack handle; throws if the nearest navigator isn’t a Stack
useTabNavigation(): ITabNavigationHandle
useNavigation() narrowed to a Tab handle; throws if the nearest navigator isn’t a Tab
useDrawerNavigation(): IDrawerNavigationHandle
useNavigation() narrowed to a Drawer handle; throws if the nearest navigator isn’t a Drawer
Signature
Description
useNavigation(): ComputedRef<INavigationHandle>
The current screen’s navigator handle (IAnyNavigatorHandle union) plus addListener and getParent
useStackNavigation(): ComputedRef<IStackNavigationHandle>
useNavigation() narrowed to a Stack handle; throws if the nearest navigator isn’t a Stack
useTabNavigation(): ComputedRef<ITabNavigationHandle>
useNavigation() narrowed to a Tab handle; throws if the nearest navigator isn’t a Tab
useDrawerNavigation(): ComputedRef<IDrawerNavigationHandle>
useNavigation() narrowed to a Drawer handle; throws if the nearest navigator isn’t a Drawer
Signature
Description
injectNavigation(): INavigationHandle
The current screen’s navigator handle (IAnyNavigatorHandle union) plus addListener and getParent
injectStackNavigation(): IStackNavigationHandle
injectNavigation() narrowed to a Stack handle; throws if the nearest navigator isn’t a Stack
injectTabNavigation(): ITabNavigationHandle
injectNavigation() narrowed to a Tab handle; throws if the nearest navigator isn’t a Tab
injectDrawerNavigation(): IDrawerNavigationHandle
injectNavigation() narrowed to a Drawer handle; throws if the nearest navigator isn’t a Drawer
Signature
Description
useNavigation(): { readonly current: INavigationHandle }
The current screen’s navigator handle (IAnyNavigatorHandle union) plus addListener and getParent. Boxed getter, read .current
useStackNavigation(): { readonly current: IStackNavigationHandle }
useNavigation() narrowed to a Stack handle; throws if the nearest navigator isn’t a Stack
useTabNavigation(): { readonly current: ITabNavigationHandle }
useNavigation() narrowed to a Tab handle; throws if the nearest navigator isn’t a Tab
useDrawerNavigation(): { readonly current: IDrawerNavigationHandle }
useNavigation() narrowed to a Drawer handle; throws if the nearest navigator isn’t a Drawer
Signature
Description
useNavigation(): Accessor<INavigationHandle>
The current screen’s navigator handle (IAnyNavigatorHandle union) plus addListener and getParent. Call to read
useStackNavigation(): Accessor<IStackNavigationHandle>
useNavigation() narrowed to a Stack handle; throws if the nearest navigator isn’t a Stack
useTabNavigation(): Accessor<ITabNavigationHandle>
useNavigation() narrowed to a Tab handle; throws if the nearest navigator isn’t a Tab
useDrawerNavigation(): Accessor<IDrawerNavigationHandle>
useNavigation() narrowed to a Drawer handle; throws if the nearest navigator isn’t a Drawer
isStackNavigatorHandle(handle)/isTabNavigatorHandle(handle)/isDrawerNavigatorHandle(handle)
are framework-agnostic type guards importable from the bare @symbiote-native/navigation package
(not /react//vue//angular//svelte) — the same guards useStackNavigation()/etc. use internally,
exposed for the one case they don’t cover: narrowing a getParent() result, which is always a
union since it doesn’t statically know the enclosing navigator’s kind. See
Nested navigators and getParent() above.
Signature
Description
useRoute(): IRoute<unknown>
The current screen’s route (name, key, params)
Signature
Description
useRoute(): ComputedRef<IRoute<unknown>>
The current screen’s route (name, key, params)
Signature
Description
injectRoute(): Signal<IRoute<unknown>>
The current screen’s route (name, key, params); call as route()
Signature
Description
useRoute(): { readonly current: IRoute<unknown> }
The current screen’s route (name, key, params). Boxed getter, read .current
Signature
Description
useRoute(): Accessor<IRoute<unknown>>
The current screen’s route (name, key, params). Call to read
Signature
Description
useIsFocused(): boolean
Whether this screen is focused right now; starts false, flips once the real focus event fires
Signature
Description
useIsFocused(): Ref<boolean>
Whether this screen is focused right now; starts false, flips once the real focus event fires. Read .value
Signature
Description
injectIsFocused(): Signal<boolean>
Whether this screen is focused right now; starts false, flips once the real focus event fires. Call as isFocused()
Signature
Description
useIsFocused(): { readonly current: boolean }
Whether this screen is focused right now; starts false, flips once the real focus event fires. Boxed getter, read .current
Signature
Description
createIsFocused(): Accessor<boolean>
Whether this screen is focused right now; starts false, flips once the real focus event fires. Call to read
Signature
Description
useFocusEffect(effect: EffectCallback): void
Runs effect on focus, runs its returned cleanup on blur. Memoize effect with useCallback — a fresh identity re-subscribes
Signature
Description
useFocusEffect(effect: () => (() => void) | void): void
Runs effect on focus, runs its returned cleanup on blur. No memoization needed
Signature
Description
injectFocusEffect(effect: () => (() => void) | void): void
Runs effect on focus, runs its returned cleanup on blur. No memoization needed
Signature
Description
useFocusEffect(effect: () => (() => void) | void): void
Runs effect on focus, runs its returned cleanup on blur. No memoization needed — a Svelte component’s script runs exactly once, so the closure is captured a single time by value
Signature
Description
createFocusEffect(effect: () => (() => void) | void): void
Runs effect on focus, runs its returned cleanup on blur. No memoization needed - a Solid component body runs exactly once, so the closure is captured a single time by value
Signature
Description
useNavigationState<T>(selector: (state: INavigatorState) => T): T
Selects a value out of the navigator’s live state; only <Stack> broadcasts live updates, <Tab>/<Drawer> stay on the initial snapshot
Signature
Description
useNavigationState<T>(selector: (state: INavigatorState) => T): ShallowRef<T>
Selects a value out of the navigator’s live state; only <Stack> broadcasts live updates, <Tab>/<Drawer> stay on the initial snapshot
Signature
Description
injectNavigationState<T>(selector: (state: INavigatorState) => T): Signal<T>
Selects a value out of the navigator’s live state; only <Stack> broadcasts live updates, <Tab>/<Drawer> stay on the initial snapshot
Signature
Description
useNavigationState<T>(selector: (state: INavigatorState) => T): { readonly current: T }
Selects a value out of the navigator’s live state; only <Stack> broadcasts live updates, <Tab>/<Drawer> stay on the initial snapshot. Boxed getter, read .current
Signature
Description
createNavigationState<T>(selector: (state: INavigatorState) => T): Accessor<T>
Selects a value out of the navigator’s live state; only <Stack> broadcasts live updates, <Tab>/<Drawer> stay on the initial snapshot. Call to read