Skip to main content
‹ Moving between screens
Hook

useNavigation()

The navigation of the screen the component is in.

SectionMoving between screensDraftDocumented, not reviewed yet: the API can change.
Import
import { useNavigation } from '@apexnative/navigation';

useNavigation() gives a screen its navigation: the commands that act from this screen. push goes into its stack, dismiss closes its modal, setOptions changes its bar, so none of them takes an id. The commands of the whole app are on the same object (switchTo, dismissAllModals, getState, the theme, the context), so a screen needs nothing else. Call it in any component inside a screen, however deep.

Usage​

src/screens/Home.tsx
import { Button } from 'react-native';
import { defineScreen, useNavigation } from '@apexnative/navigation';

function Home() {
const navigation = useNavigation();
const open = () =>
navigation.push('Details', {
props: { id: '42' },
transition: 'fade',
});
return <Button title="Details" onPress={open} />;
}

export const HomeScreen = defineScreen({
component: Home,
options: (s) => s.title('Home'),
});

A tap on Details fades Details in over Home; back fades it out. The same push from a list row inside Home works too: the row is in Home's screen, so its useNavigation() is Home's.

The returned object stays the same between renders of the screen, so it can go into the dependencies of useCallback and useEffect without running them again.

info · Coming from React Navigation

It looks like React Navigation's useNavigation, with two differences. Screens are named by their name in screens, and push always adds a screen (React Navigation's navigate goes back to an existing one; here a key in defineScreen does that). And the commands return promises that resolve when the move has ended. See From React Navigation.

iOS
Android

Signature​

function useNavigation(): ScreenNavigation
Defined in src/api/hooks/useNavigation.ts ↗

It takes no arguments and returns a ScreenNavigation, described below.

Outside a screen it throws: "useNavigation is for the screens of createNavigation, and this component is not in one". That happens in a component that appProvider renders around the screens, in content a portal host in appProvider renders, or in a test that renders the component alone. Code outside the screens uses navigation.

This screen​

screen​

screen: ScreenInfo
TypeScreenInfo Required.PlatformsiOSiPadOSAndroid

The screen the commands act from, as { id, name }: the same object useScreen() returns. The id is of this showing: two Profiles pushed one over the other have the same name and different ids.

const { id, name } = navigation.screen;

setOptions​

setOptions(options: ScreenOptionsInput<object>): void
PlatformsiOSiPadOSAndroid

Changes the options of this screen once, from an event: a press, an answer from the server. It takes a chain, as in defineScreen, or options as an object; they merge over what the screen has.

const rename = async (name: string) => {
await saveName(name); // your own request
navigation.setOptions((s) => s.title(name));
};

The bar shows the new name once it is saved. When the options follow the state of the component (a title from loaded data, a button that turns on with a selection), use useScreenOptions instead: it keeps them in step on every render. See Screen options.

The stack​

The stack commands act on the stack this screen is in. Two kinds of screens have no stack of their own and act from elsewhere:

  • An overlay acts from the screen the user sees, as navigation does: a banner opens a chat in the stack under it.
  • A menu of a side menu acts from its center, and the menu closes as the center changes. See A pick in the menu.

Any other screen that isn't in a stack (a screen alone in a tab, a modal with stack: false) rejects the stack commands: "Home is not in a stack".

push​

push<Name extends ScreenName>(name: Name, ...args: ShowArgs<Name, PushConfig>): Promise<string>
PlatformsiOSiPadOSAndroid

Shows a screen over this one in its stack. The second argument takes its props and the settings of this push (PushConfig): transition, how it comes and goes, and options, its options for this showing.

const id = await navigation.push('Profile', {
props: { userId },
options: (s) => s.title(userName),
});

It resolves, once Profile is in place, with the id of the screen shown. props is required when the screen has a required prop.

  • The user moved on. The push goes into this screen's stack even when the user has gone to another tab meanwhile: the screen shows there when the user comes back. That is what you want after a load the screen started.
  • This screen has closed before the push, such as after a slow request: it rejects, "the screen 'Component12' is gone".
  • A double tap pushes two screens. Give the screen a key in defineScreen, and a push of it goes back to the one that shows instead: see One screen per item.
  • A screen that isn't on top pushes into its stack all the same: the new screen goes on top, over the screens already there.

pop​

pop(): Promise<string>
PlatformsiOSiPadOSAndroid

Goes back to the screen before this one in its stack, as the back button does. Resolves with the id of the screen that left.

const save = async () => {
await saveProfile(form); // your own request
await navigation.pop();
};

On the first screen of its stack it rejects: "Home is the first screen of its stack, there is nothing to go back to". A screen that is first in a modal closes it with dismiss, and one that can be either uses goBack.

popTo​

popTo(screenId: string): Promise<string>
PlatformsiOSiPadOSAndroid

Goes back to a screen of the stack by its id: the one push resolved with, or screen.id that a screen passed on.

// Cart pushes Checkout with its own id
navigation.push('Checkout', {
props: { cartId: navigation.screen.id },
});

// on the last checkout screen
await navigation.popTo(cartId);

It rejects when that screen has closed.

popToRoot​

popToRoot(): Promise<string>
PlatformsiOSiPadOSAndroid

Goes back to the first screen of this stack, past all the others at once: "Done" at the end of a flow of several screens.

replace​

replace<Name extends ScreenName>(
name: Name,
...args: ShowArgs<Name, PushConfig>
): Promise<string>
PlatformsiOSiPadOSAndroid

Shows a screen in place of the whole history of this stack, this screen included: it comes in as a push does and is alone in the stack, so there is no back.

// on Payment, once the payment went through
await navigation.replace('OrderPlaced', { props: { orderId } });

Back can't lead to a second payment. Resolves with the id of the new screen. From a side menu, replace is how a pick starts the center over with a new section.

setStack​

setStack<const Entries extends readonly StackEntry[]>(
...entries: CheckedEntries<Entries>
): Promise<string>
PlatformsiOSiPadOSAndroid

Replaces the screens of this stack: the last one shows, back goes down the others. Each entry is a name, or Screen(name, props).

import { Screen } from '@apexnative/navigation';

await navigation.setStack('Inbox', Screen('Chat', { chatId }));

Every screen of the new stack is new, this one included. In a modal, setStack replaces its first screen too, and the onDismiss of the modal runs. See A whole stack at once.

canPop​

canPop(): boolean
PlatformsiOSiPadOSAndroid

Whether there is a screen under this one in its stack. It answers at once, so a screen can choose what to render:

{navigation.canPop() && (
<Button title="Back" onPress={() => navigation.pop()} />
)}

The answer is of the moment of the render; the screen doesn't render again when it changes.

Going back​

goBack​

goBack(): Promise<string>
PlatformsiOSiPadOSAndroid

What the back button would do here: pop() when there is a screen under this one in its stack, dismiss() when it is the first screen of a modal or in an overlay. For a screen that opens both ways, pushed in one place and in a modal in another:

const send = async () => {
await sendMessage(); // your own request
await navigation.goBack();
};

Pushed, Compose goes back to the screen under it; in a modal, the modal closes. On the first screen of the root it rejects: there is nothing to go back to.

canGoBack​

canGoBack(): boolean
PlatformsiOSiPadOSAndroid

Whether goBack() has something to do: canPop() or canDismiss(). Answers at once.

dismiss​

dismiss(): Promise<string>
PlatformsiOSiPadOSAndroid

Closes the modal or the overlay this screen is in, with all its screens, from any screen of it. Resolves once it is gone.

<Button title="Cancel" onPress={() => navigation.dismiss()} />

On a screen that isn't in a modal or an overlay it rejects: "Home is not in a modal or an overlay". A modal whose screen keeps the user with usePreventLeave still closes with dismiss: the hold is on the user's ways out, not on the app's.

canDismiss​

canDismiss(): boolean
PlatformsiOSiPadOSAndroid

Whether this screen is in a modal or an overlay, so dismiss() has one to close. Answers at once:

{navigation.canDismiss() && (
<Button title="Close" onPress={() => navigation.dismiss()} />
)}

Modals​

showModal​

showModal<Name extends ScreenName>(
name: Name,
...args: ShowArgs<Name, ModalConfig>
): Promise<string>
PlatformsiOSiPadOSAndroid

Shows a screen over everything, in a stack of its own unless stack: false. It acts from no screen, so it is the same as navigation.showModal, with its settings (ModalConfig).

navigation.showModal('Compose', { closeButton: true });

Compose slides up with a close button. Inside it, useNavigation() is Compose's: its push goes into the modal's stack, its dismiss closes the modal. See Modals.

dismissModal​

dismissModal(modalId: string): Promise<string>
PlatformsiOSiPadOSAndroid

Closes a modal by its id or the id of a screen in it. From a screen of the modal, dismiss needs no id. As navigation.dismissModal.

dismissAllModals​

dismissAllModals(): Promise<string>
PlatformsiOSiPadOSAndroid

Closes every modal at once. As navigation.dismissAllModals.

Overlays​

showOverlay​

showOverlay<Name extends ScreenName>(
name: Name,
...args: ShowArgs<Name, OverlayConfig>
): Promise<string>
PlatformsiOSiPadOSAndroid

Shows a screen over everything without taking the place of any. As navigation.showOverlay; the overlay closes itself with its own useNavigation().dismiss().

src/screens/Toast.tsx
import { useEffect } from 'react';
import { Text } from 'react-native';
import { useNavigation } from '@apexnative/navigation';

function Toast({ text }: { text: string }) {
const navigation = useNavigation();
useEffect(() => {
const timer = setTimeout(() => navigation.dismiss(), 2000);
return () => clearTimeout(timer);
}, [navigation]);
return <Text>{text}</Text>;
}

dismissOverlay​

dismissOverlay(overlayId: string): Promise<string>
PlatformsiOSiPadOSAndroid

Closes an overlay by its id. As navigation.dismissOverlay.

dismissAllOverlays​

dismissAllOverlays(): Promise<string>
PlatformsiOSiPadOSAndroid

Closes every overlay. As navigation.dismissAllOverlays.

Roots​

switchTo​

switchTo<Name extends RootName>(
name: Name,
...param: RootParam<RegisteredRoots[Name]>
): Promise<void>
switchTo<const Layout extends Exclude<RootLayout, string>>(
layout: Layout & NoInfer<CheckedScreens<UsesOfRoot<Layout>, RegisteredScreens>>
): Promise<void>
PlatformsiOSiPadOSAndroid

Replaces the whole navigation with another root, this screen included: it unmounts. As navigation.switchTo.

const signOut = async () => {
await clearSession(); // your own
await navigation.switchTo('signedOut');
};

Code after the await runs in a screen that has gone: set state there only in a component that stays, and don't make more commands of this screen.

Tabs​

selectTab​

selectTab(tab: number | string): Promise<void>
PlatformsiOSiPadOSAndroid

Selects a tab of the tabs this screen is in, by index or by the id of Tab().id(). Resolves once the state shows it.

await navigation.selectTab('inbox');

The commands after it are still this screen's: a push after it goes into this screen's stack, not into Inbox's. To act in the new tab, follow with navigation, which acts where the user is. See Selecting a tab.

setTabBadge​

setTabBadge(badge: string | number | null, tab?: string): Promise<void>
PlatformsiOSiPadOSAndroid

The badge of this screen's tab, or of the tab with the id; null and the number 0 take it away, the text '0' shows "0".

useEffect(() => {
navigation.setTabBadge(unread);
}, [navigation, unread]);

The Inbox tab shows the number of unread messages, and no badge at 0. See Badges and dots.

Split view​

showInColumn​

showInColumn<Name extends ScreenName>(
name: Name,
...args: ShowArgs<Name, ColumnConfig>
): Promise<string>
PlatformsiOSiPadOSAndroid

Shows a screen in a column of the split view this screen is in, in place of what the column shows: the details beside a list. column (ColumnConfig) is 'secondary', the default, or 'supplementary', the middle one of three.

function Settings() {
const navigation = useNavigation();
const open = (section: string) =>
navigation.showInColumn('Details', { props: { section } });
return <Button title="Profile" onPress={() => open('profile')} />;
}

On an iPad, Details shows beside the list, and a sidebar over it goes away. Collapsed into one stack (an iPhone, a narrow window) Details is pushed, and back comes to Settings. Android The secondary pane comes over the primary one, and has no supplementary column. Outside a split view, Details is pushed over this screen. See Split view.

showColumn​

showColumn(column: SplitColumn): Promise<void>
PlatformsiOSiPadOSAndroid

Shows a column of the split view this screen is in: the sidebar ('primary'), the middle column ('supplementary'), the inspector. Resolves once the columns have moved. Android The primary pane only; the other columns resolve and change nothing.

hideColumn​

hideColumn(column: SplitColumn): Promise<void>
PlatformsiOSiPadOSAndroid

Hides a column of the split view this screen is in: hideColumn('primary') leaves the item alone on screen, for reading. Resolves once the columns have moved.

openMenu​

openMenu(side: 'left' | 'right'): Promise<void>
PlatformsiOSiPadOSAndroid

Opens the 'left' or 'right' menu of the side menu this screen is in. Resolves once the menu is open; at once when it is open already. A screen outside a side menu rejects: "Home is not in a side menu".

// in defineScreen
options: (s) =>
s.leftButtons({
id: 'menu',
icon: { ios: 'line.3.horizontal', android: 'ic_menu' },
onPress: ({ navigation }) => navigation.openMenu('left'),
}),

The navigation a button's onPress gets is this object for the button's screen. See Side menu.

closeMenu​

closeMenu(): Promise<void>
PlatformsiOSiPadOSAndroid

Closes the menu that is open; resolves once it is closed and getState() shows the center, at once when none is. A pick in the menu that moves the center closes it by itself.

The rest of the app​

These act on the whole app, the same as on navigation.

setTheme​

setTheme(theme: NavigationTheme): void
PlatformsiOSiPadOSAndroid

Changes the theme of the navigation; the new theme replaces the old one. A Settings screen with an accent color picker calls it. See navigation.setTheme.

setDirection​

setDirection(direction: NavigationDirection): void
PlatformsiOSiPadOSAndroid

Changes the direction of the app. See navigation.setDirection.

getContext​

getContext(): RegisteredContext
PlatformsiOSiPadOSAndroid

The app's context now, for a handler. A component that renders from it uses useAppContext, which renders again when it changes; getContext() in a render doesn't.

setContext​

setContext(next: RegisteredContext | ((previous: RegisteredContext) => RegisteredContext)): void
PlatformsiOSiPadOSAndroid

Changes the app's context: the value replaces it, or a function of the previous one returns the new one.

navigation.setContext((context) => ({ ...context, signedIn: true }));

getState​

getState(): NavigationState
PlatformsiOSiPadOSAndroid

What the user sees now: the screen and the selected tab. It may be another screen than this one: a modal over it, another tab. After a command resolves, it shows what the command did: after await navigation.push('Details') it names Details. To render from it, use useIsFocused() (this screen is the one the user sees) or useNavigationState.

on​

on<Name extends NavigationEventName>(
name: Name,
handler: (event: NavigationEvents[Name]) => void
): () => void
PlatformsiOSiPadOSAndroid

Listens to an event of the whole navigation; returns the function that stops. In a screen, the hooks are simpler: useScreenEvent for this screen's events and useNavigationEvent for all of them stop by themselves when the screen unmounts. See Where to listen.

getPendingLink(): PendingLink | null
PlatformsiOSiPadOSAndroid

The link that waits, or null. A screen that shows it uses usePendingLink, which renders again when it changes.

openPendingLink(): Promise<boolean>
PlatformsiOSiPadOSAndroid

Runs the waiting link now and forgets it; resolves with false when none waits. See navigation.openPendingLink.

clearPendingLink(): void
PlatformsiOSiPadOSAndroid

Forgets the waiting link.

A screen that isn't on top​

A screen stays mounted under the screens pushed over it, under a modal, and in a tab the user left. Its commands still act from it:

  • A timer on Home that pushes Details while the user is on Profile, over Home, pushes Details over Profile, in Home's stack. A push that should only happen while the screen shows checks useIsFocused() or waits for useScreenAppear.
  • A screen under a modal pushes into its own stack, under the modal: the user sees it after the modal closes.
  • The commands of a screen that has closed reject, "the screen '…' is gone". A request that ends after the user went back should catch it, or check that the screen is still mounted.

See also​

Generated from the types and their comments. Edit the source ↗