Skip to content

Hooks & focus

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. All four read from the nearest enclosing <Stack>, <Tab>, or <Drawer> screen and throw if called outside one.

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>
import { useStackNavigation, useRoute } from '@symbiote-native/navigation/react';
function ProfileScreen() {
const navigation = useStackNavigation();
const route = useRoute();
return (
<ActionButton
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.

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
import { useCallback, useState } from 'react';
import { useFocusEffect } from '@symbiote-native/navigation/react';
function HooksDemoScreen() {
const [focusCount, setFocusCount] = useState(0);
useFocusEffect(
useCallback(() => {
setFocusCount(count => count + 1);
return () => {
/* runs on blur */
};
}, []),
);
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
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>);
}

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();
return (
<ActionButton
title="Pop parent Stack"
onPress={() => {
if (parent !== undefined && isStackNavigatorHandle(parent)) parent.pop();
}}
/>
);
}

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() — same one-hop semantics in all three.

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

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
useIsFocused(): boolean Whether this screen is focused right now; starts false, flips once the real focus event fires
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
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