From df4793804eb9bf572f7e923d057aa78cdcc6d642 Mon Sep 17 00:00:00 2001 From: Satyajit Sahoo Date: Tue, 26 May 2026 22:54:59 +0200 Subject: [PATCH] Document usage with standard-navigation --- .../version-7.x/custom-navigators.md | 6 + .../version-7.x/standard-navigator.md | 266 ++++++++++++++++++ .../version-8.x/custom-navigators.md | 6 + .../version-8.x/standard-navigator.md | 253 +++++++++++++++++ versioned_sidebars/version-7.x-sidebars.json | 7 +- versioned_sidebars/version-8.x-sidebars.json | 7 +- 6 files changed, 543 insertions(+), 2 deletions(-) create mode 100644 versioned_docs/version-7.x/standard-navigator.md create mode 100644 versioned_docs/version-8.x/standard-navigator.md diff --git a/versioned_docs/version-7.x/custom-navigators.md b/versioned_docs/version-7.x/custom-navigators.md index fd7ffb2285..d74dc903cb 100755 --- a/versioned_docs/version-7.x/custom-navigators.md +++ b/versioned_docs/version-7.x/custom-navigators.md @@ -10,6 +10,12 @@ React Navigation provides a few built-in navigators, but they might not always f A custom navigator behaves just like a built-in navigator, and can be used in the same way. This means you can define screens the same way, use [route](route-object.md) and [navigation](navigation-object.md) objects in your screens, and navigate between screens with familiar API. The navigator will also be able to handle [deep linking](deep-linking.md), [state persistence](state-persistence.md), and other features that built-in navigators support. +:::tip + +If you're publishing a navigator library, see [Standard navigator](standard-navigator.md) to build a navigator that can integrate with multiple navigation libraries such as React Navigation and Expo Router. + +::: + ## Overview Under the hood, navigators are plain React components that use the [`useNavigationBuilder`](#usenavigationbuilder) hook. diff --git a/versioned_docs/version-7.x/standard-navigator.md b/versioned_docs/version-7.x/standard-navigator.md new file mode 100644 index 0000000000..244a24735a --- /dev/null +++ b/versioned_docs/version-7.x/standard-navigator.md @@ -0,0 +1,266 @@ +--- +id: standard-navigator +title: Standard navigator +sidebar_label: Standard navigator +--- + +The [`standard-navigation`](https://github.com/react-navigation/standard-navigation/) package provides a standard API for writing navigators that can work with multiple navigation libraries, such as React Navigation and Expo Router. + +This is primarily useful for library authors. If you don't plan to publish the library and are building a navigator for use with your existing React Navigation setup, see [Custom navigators](custom-navigators.md) instead. + +## Project structure + +Install `standard-navigation` as a regular dependency in your navigator library: + +```bash npm2yarn +npm install standard-navigation +``` + +Keep the standard navigator implementation independent from any specific navigation library. Then expose separate entry points for each navigation library: + +```text +my-navigator/ + package.json + src/ + MyTabNavigator.tsx + react-navigation.tsx + expo-router.tsx +``` + +Your package exports can point to those entry points: + +```json +{ + "exports": { + ".": { + "types": "./lib/typescript/index.d.ts", + "default": "./lib/module/index.js" + }, + "./react-navigation": { + "types": "./lib/typescript/react-navigation.d.ts", + "default": "./lib/module/react-navigation.js" + }, + "./expo-router": { + "types": "./lib/typescript/expo-router.d.ts", + "default": "./lib/module/expo-router.js" + } + }, + "dependencies": { + "standard-navigation": "^0.0.5" + } +} +``` + +## Standard navigator implementation + +The standard navigator file should export the navigator object created with `createStandardNavigator`. This file shouldn't import React Navigation or Expo Router APIs. + +To create a standard navigator, use the `createStandardNavigator` function from `standard-navigation`, and pass it a component that renders the desired UI. + +Example: + +```tsx title="src/MyTabNavigator.tsx" +import * as React from 'react'; +import { + Text, + Pressable, + type StyleProp, + StyleSheet, + View, + type ViewStyle, +} from 'react-native'; +import { createStandardNavigator } from 'standard-navigation'; + +export type MyTabOptions = { + title?: string; +}; + +export type MyTabEventMap = { + tabPress: { + data: { isAlreadyFocused: boolean }; + canPreventDefault: true; + }; +}; + +export type MyTabNavigatorProps = { + tabBarStyle?: StyleProp; + contentStyle?: StyleProp; +}; + +export const MyTabNavigator = createStandardNavigator< + MyTabOptions, + MyTabEventMap, + MyTabNavigatorProps +>(({ state, descriptors, actions, emitter, tabBarStyle, contentStyle }) => { + return ( + + + {state.routes.map((route, index) => ( + { + const isFocused = state.index === index; + const event = emitter.emit({ + type: 'tabPress', + target: route.key, + canPreventDefault: true, + data: { isAlreadyFocused: isFocused }, + }); + + if (!isFocused && !event.defaultPrevented) { + actions.navigate(route.name, route.params); + } + }} + style={{ flex: 1 }} + > + {descriptors[route.key].options.title ?? route.name} + + ))} + + + {state.routes.map((route, i) => { + return ( + + {descriptors[route.key].render()} + + ); + })} + + + ); +}); +``` + +The `createStandardNavigator` function accepts three generic arguments: + +- `MyTabOptions` - The type of the options available for each screen. +- `MyTabEventMap` - The type of the events that can be emitted by the navigator. +- `MyTabNavigatorProps` - The type of any additional props accepted by the navigator. + +The callback receives `state`, `descriptors`, `actions`, and `emitter` from the navigation library integration: + +- `state.routes` contains `{ key, name, params, href }` objects. +- `descriptors[route.key].options` contains the screen options. +- `descriptors[route.key].render()` renders the screen. +- `actions.navigate(name, params)` and `actions.back()` perform navigation. +- `emitter.emit(...)` emits navigator events to screen listeners. + +:::note + +For stack navigators, `state.routes` array contains the history of visited screens until `state.index`, and the route objects after `state.index` represent [preloaded](navigation-actions.md#preload) routes. + +::: + +## React Navigation entry point + +The React Navigation entry point should wrap the standard navigator with `createStandardNavigationFactories` from `@react-navigation/native`: + +```tsx title="src/react-navigation.tsx" +import { + createStandardNavigationFactories, + type NavigationProp, + type ParamListBase, + type RouteProp, + type StandardNavigationTypeBagBase, + type TabActionHelpers, + type TabNavigationState, + TabRouter, + type TabRouterOptions, +} from '@react-navigation/native'; + +import { + MyTabNavigator, + type MyTabEventMap, + type MyTabNavigatorProps, + type MyTabOptions, +} from './MyTabNavigator'; + +export type MyTabNavigationProp< + ParamList extends ParamListBase, + RouteName extends keyof ParamList = keyof ParamList, + NavigatorID extends string | undefined = undefined, +> = NavigationProp< + ParamList, + RouteName, + NavigatorID, + TabNavigationState, + MyTabOptions, + MyTabEventMap +> & + TabActionHelpers; + +export type MyTabScreenProps< + ParamList extends ParamListBase, + RouteName extends keyof ParamList = keyof ParamList, + NavigatorID extends string | undefined = undefined, +> = { + navigation: MyTabNavigationProp; + route: RouteProp; +}; + +export interface MyTabTypeBag extends StandardNavigationTypeBagBase { + State: TabNavigationState; + ActionHelpers: TabActionHelpers; + ScreenOptions: MyTabOptions; + EventMap: MyTabEventMap; + RouterOptions: TabRouterOptions; +} + +export const { + createNavigator: createMyTabNavigator, + createScreen: createMyTabScreen, +} = createStandardNavigationFactories( + MyTabNavigator, + TabRouter +); +``` + +The `createStandardNavigationFactories` function accepts two generic arguments: + +- The type bag for the navigator (e.g. `MyTabTypeBag`), which includes the state, action helpers, screen options, event map, and router options types. +- The type of any additional props accepted by the navigator (e.g. `MyTabNavigatorProps`). + +It accepts 3 arguments: + +- The standard navigator component. +- The router factory function from React Navigation (e.g. `TabRouter`, `StackRouter`, etc.). +- An optional function to map `{ navigation, state }` to custom props for the navigator component, in case you need any specific state or action helpers not available in the standard ones. + +It returns an object with `createNavigator` and `createScreen` functions that can be used to create the navigator and screens for React Navigation. These should be exported from the entry point. + +Additionally, you can export custom navigation prop and screen prop types (e.g. `MyTabNavigationProp` and `MyTabScreenProps`) that can be used by consumers for type annotations. + +Consumers can then use the React Navigation entry point: + +```tsx static2dynamic +import { createStaticNavigation } from '@react-navigation/native'; +import { + createMyTabNavigator, + createMyTabScreen, +} from 'my-navigator/react-navigation'; + +const MyTabs = createMyTabNavigator({ + screens: { + Home: createMyTabScreen({ + screen: HomeScreen, + options: { title: 'Home' }, + }), + Feed: createMyTabScreen({ + screen: FeedScreen, + options: { title: 'Feed' }, + }), + }, +}); + +const Navigation = createStaticNavigation(MyTabs); +``` + +## Expo Router entry point + +Work in progress. diff --git a/versioned_docs/version-8.x/custom-navigators.md b/versioned_docs/version-8.x/custom-navigators.md index 3f9e8dc25f..35722295e7 100755 --- a/versioned_docs/version-8.x/custom-navigators.md +++ b/versioned_docs/version-8.x/custom-navigators.md @@ -10,6 +10,12 @@ React Navigation provides a few built-in navigators, but they might not always f A custom navigator behaves just like a built-in navigator, and can be used in the same way. This means you can define screens the same way, use [route](route-object.md) and [navigation](navigation-object.md) objects in your screens, and navigate between screens with familiar API. The navigator will also be able to handle [deep linking](deep-linking.md), [state persistence](state-persistence.md), and other features that built-in navigators support. +:::tip + +If you're publishing a navigator library, see [Standard navigator](standard-navigator.md) to build a navigator that can integrate with multiple navigation libraries such as React Navigation and Expo Router. + +::: + ## Overview Under the hood, navigators are plain React components that use the [`useNavigationBuilder`](#usenavigationbuilder) hook. diff --git a/versioned_docs/version-8.x/standard-navigator.md b/versioned_docs/version-8.x/standard-navigator.md new file mode 100644 index 0000000000..c6697527b8 --- /dev/null +++ b/versioned_docs/version-8.x/standard-navigator.md @@ -0,0 +1,253 @@ +--- +id: standard-navigator +title: Standard navigator +sidebar_label: Standard navigator +--- + +The [`standard-navigation`](https://github.com/react-navigation/standard-navigation/) package provides a standard API for writing navigators that can work with multiple navigation libraries, such as React Navigation and Expo Router. + +This is primarily useful for library authors. If you don't plan to publish the library and are building a navigator for use with your existing React Navigation setup, see [Custom navigators](custom-navigators.md) instead. + +## Project structure + +Install `standard-navigation` as a regular dependency in your navigator library: + +```bash npm2yarn +npm install standard-navigation +``` + +Keep the standard navigator implementation independent from any specific navigation library. Then expose separate entry points for each navigation library: + +```text +my-navigator/ + package.json + src/ + MyTabNavigator.tsx + react-navigation.tsx + expo-router.tsx +``` + +Your package exports can point to those entry points: + +```json +{ + "exports": { + ".": { + "types": "./lib/typescript/index.d.ts", + "default": "./lib/module/index.js" + }, + "./react-navigation": { + "types": "./lib/typescript/react-navigation.d.ts", + "default": "./lib/module/react-navigation.js" + }, + "./expo-router": { + "types": "./lib/typescript/expo-router.d.ts", + "default": "./lib/module/expo-router.js" + } + }, + "dependencies": { + "standard-navigation": "^0.0.5" + } +} +``` + +## Standard navigator implementation + +The standard navigator file should export the navigator object created with `createStandardNavigator`. This file shouldn't import React Navigation or Expo Router APIs. + +To create a standard navigator, use the `createStandardNavigator` function from `standard-navigation`, and pass it a component that renders the desired UI. + +Example: + +```tsx title="src/MyTabNavigator.tsx" +import * as React from 'react'; +import { + Text, + Pressable, + type StyleProp, + View, + type ViewStyle, +} from 'react-native'; +import { createStandardNavigator } from 'standard-navigation'; + +export type MyTabOptions = { + title?: string; +}; + +export type MyTabEventMap = { + tabPress: { + data: { isAlreadyFocused: boolean }; + canPreventDefault: true; + }; +}; + +export type MyTabNavigatorProps = { + tabBarStyle?: StyleProp; + contentStyle?: StyleProp; +}; + +export const MyTabNavigator = createStandardNavigator< + MyTabOptions, + MyTabEventMap, + MyTabNavigatorProps +>(({ state, descriptors, actions, emitter, tabBarStyle, contentStyle }) => { + return ( + + + {state.routes.map((route, index) => ( + { + const isFocused = state.index === index; + const event = emitter.emit({ + type: 'tabPress', + target: route.key, + canPreventDefault: true, + data: { isAlreadyFocused: isFocused }, + }); + + if (!isFocused && !event.defaultPrevented) { + actions.navigate(route.name, route.params); + } + }} + style={{ flex: 1 }} + > + {descriptors[route.key].options.title ?? route.name} + + ))} + + + {state.routes.map((route, i) => { + return ( + + {descriptors[route.key].render()} + + ); + })} + + + ); +}); +``` + +The `createStandardNavigator` function accepts three generic arguments: + +- `MyTabOptions` - The type of the options available for each route descriptor. +- `MyTabEventMap` - The type of the events that can be emitted by the navigator. +- `MyTabNavigatorProps` - The type of any additional props accepted by the navigator. + +The callback receives `state`, `descriptors`, `actions`, and `emitter` from the navigation library integration: + +- `state.routes` contains `{ key, name, params, href }` objects. +- `descriptors[route.key].options` contains the screen options. +- `descriptors[route.key].render()` renders the screen. +- `actions.navigate(name, params)` and `actions.back()` perform navigation. +- `emitter.emit(...)` emits navigator events to screen listeners. + +## React Navigation entry point + +The React Navigation entry point should wrap the standard navigator with `createStandardNavigationFactories` from `@react-navigation/native`: + +```tsx title="src/react-navigation.tsx" +import { + createStandardNavigationFactories, + type NavigationProp, + type ParamListBase, + type RouteProp, + type StandardNavigationTypeBagBase, + type TabActionHelpers, + type TabNavigationState, + TabRouter, + type TabRouterOptions, +} from '@react-navigation/native'; + +import { + MyTabNavigator, + type MyTabEventMap, + type MyTabNavigatorProps, + type MyTabOptions, +} from './MyTabNavigator'; + +export type MyTabNavigationProp< + ParamList extends ParamListBase, + RouteName extends keyof ParamList = keyof ParamList, +> = NavigationProp< + ParamList, + RouteName, + TabNavigationState, + MyTabOptions, + MyTabEventMap, + TabActionHelpers +>; + +export type MyTabScreenProps< + ParamList extends ParamListBase, + RouteName extends keyof ParamList = keyof ParamList, +> = { + navigation: MyTabNavigationProp; + route: RouteProp; +}; + +export interface MyTabTypeBag extends StandardNavigationTypeBagBase { + State: TabNavigationState; + ActionHelpers: TabActionHelpers; + ScreenOptions: MyTabOptions; + EventMap: MyTabEventMap; + RouterOptions: TabRouterOptions; +} + +export const { + createNavigator: createMyTabNavigator, + createScreen: createMyTabScreen, +} = createStandardNavigationFactories( + MyTabNavigator, + TabRouter +); +``` + +The `createStandardNavigationFactories` function accepts two generic arguments: + +- The type bag for the navigator (e.g. `MyTabTypeBag`), which includes the state, action helpers, screen options, event map, and router options types. +- The type of any additional props accepted by the navigator (e.g. `MyTabNavigatorProps`). + +It accepts 3 arguments: + +- The standard navigator component. +- The router factory function from React Navigation (e.g. `TabRouter`, `StackRouter`, etc.). +- An optional function to map `{ navigation, state }` to custom props for the navigator component, in case you need any specific state or action helpers not available in the standard ones. + +It returns an object with `createNavigator` and `createScreen` functions that can be used to create the navigator and screens for React Navigation. These should be exported from the entry point. + +Additionally, you can export custom navigation prop and screen prop types (e.g. `MyTabNavigationProp` and `MyTabScreenProps`) that can be used by consumers for type annotations. + +Consumers can then use the React Navigation entry point: + +```tsx static2dynamic +import { createStaticNavigation } from '@react-navigation/native'; +import { + createMyTabNavigator, + createMyTabScreen, +} from 'my-navigator/react-navigation'; + +const MyTabs = createMyTabNavigator({ + screens: { + Home: createMyTabScreen({ + screen: HomeScreen, + options: { title: 'Home' }, + }), + Feed: createMyTabScreen({ + screen: FeedScreen, + options: { title: 'Feed' }, + }), + }, +}); + +const Navigation = createStaticNavigation(MyTabs); +``` + +## Expo Router entry point + +Work in progress. diff --git a/versioned_sidebars/version-7.x-sidebars.json b/versioned_sidebars/version-7.x-sidebars.json index 11badfe187..9ca9e7323f 100644 --- a/versioned_sidebars/version-7.x-sidebars.json +++ b/versioned_sidebars/version-7.x-sidebars.json @@ -157,7 +157,12 @@ "customProps": { "icon": "build" }, - "items": ["navigation-state", "custom-routers", "custom-navigators"] + "items": [ + "navigation-state", + "custom-routers", + "custom-navigators", + "standard-navigator" + ] }, { "type": "category", diff --git a/versioned_sidebars/version-8.x-sidebars.json b/versioned_sidebars/version-8.x-sidebars.json index 33636a13ac..7fed6afe55 100644 --- a/versioned_sidebars/version-8.x-sidebars.json +++ b/versioned_sidebars/version-8.x-sidebars.json @@ -163,7 +163,12 @@ "customProps": { "icon": "build" }, - "items": ["navigation-state", "custom-routers", "custom-navigators"] + "items": [ + "navigation-state", + "custom-routers", + "custom-navigators", + "standard-navigator" + ] }, { "type": "category",