|
| 1 | +--- |
| 2 | +id: standard-navigator |
| 3 | +title: Standard navigator |
| 4 | +sidebar_label: Standard navigator |
| 5 | +--- |
| 6 | + |
| 7 | +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. |
| 8 | + |
| 9 | +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. |
| 10 | + |
| 11 | +## Project structure |
| 12 | + |
| 13 | +Install `standard-navigation` as a regular dependency in your navigator library: |
| 14 | + |
| 15 | +```bash npm2yarn |
| 16 | +npm install standard-navigation |
| 17 | +``` |
| 18 | + |
| 19 | +Keep the standard navigator implementation independent from any specific navigation library. Then expose separate entry points for each navigation library: |
| 20 | + |
| 21 | +```text |
| 22 | +my-navigator/ |
| 23 | + package.json |
| 24 | + src/ |
| 25 | + MyTabNavigator.tsx |
| 26 | + react-navigation.tsx |
| 27 | + expo-router.tsx |
| 28 | +``` |
| 29 | + |
| 30 | +Your package exports can point to those entry points: |
| 31 | + |
| 32 | +```json |
| 33 | +{ |
| 34 | + "exports": { |
| 35 | + ".": { |
| 36 | + "types": "./lib/typescript/index.d.ts", |
| 37 | + "default": "./lib/module/index.js" |
| 38 | + }, |
| 39 | + "./react-navigation": { |
| 40 | + "types": "./lib/typescript/react-navigation.d.ts", |
| 41 | + "default": "./lib/module/react-navigation.js" |
| 42 | + }, |
| 43 | + "./expo-router": { |
| 44 | + "types": "./lib/typescript/expo-router.d.ts", |
| 45 | + "default": "./lib/module/expo-router.js" |
| 46 | + } |
| 47 | + }, |
| 48 | + "dependencies": { |
| 49 | + "standard-navigation": "^0.0.5" |
| 50 | + } |
| 51 | +} |
| 52 | +``` |
| 53 | + |
| 54 | +## Standard navigator implementation |
| 55 | + |
| 56 | +The standard navigator file should export the navigator object created with `createStandardNavigator`. This file shouldn't import React Navigation or Expo Router APIs. |
| 57 | + |
| 58 | +To create a standard navigator, use the `createStandardNavigator` function from `standard-navigation`, and pass it a component that renders the desired UI. |
| 59 | + |
| 60 | +Example: |
| 61 | + |
| 62 | +```tsx title="src/MyTabNavigator.tsx" |
| 63 | +import * as React from 'react'; |
| 64 | +import { |
| 65 | + Text, |
| 66 | + Pressable, |
| 67 | + type StyleProp, |
| 68 | + StyleSheet, |
| 69 | + View, |
| 70 | + type ViewStyle, |
| 71 | +} from 'react-native'; |
| 72 | +import { createStandardNavigator } from 'standard-navigation'; |
| 73 | + |
| 74 | +export type MyTabOptions = { |
| 75 | + title?: string; |
| 76 | +}; |
| 77 | + |
| 78 | +export type MyTabEventMap = { |
| 79 | + tabPress: { |
| 80 | + data: { isAlreadyFocused: boolean }; |
| 81 | + canPreventDefault: true; |
| 82 | + }; |
| 83 | +}; |
| 84 | + |
| 85 | +export type MyTabNavigatorProps = { |
| 86 | + tabBarStyle?: StyleProp<ViewStyle>; |
| 87 | + contentStyle?: StyleProp<ViewStyle>; |
| 88 | +}; |
| 89 | + |
| 90 | +export const MyTabNavigator = createStandardNavigator< |
| 91 | + MyTabOptions, |
| 92 | + MyTabEventMap, |
| 93 | + MyTabNavigatorProps |
| 94 | +>(({ state, descriptors, actions, emitter, tabBarStyle, contentStyle }) => { |
| 95 | + return ( |
| 96 | + <View style={{ flex: 1 }}> |
| 97 | + <View style={[{ flexDirection: 'row' }, tabBarStyle]}> |
| 98 | + {state.routes.map((route, index) => ( |
| 99 | + <Pressable |
| 100 | + key={route.key} |
| 101 | + onPress={() => { |
| 102 | + const isFocused = state.index === index; |
| 103 | + const event = emitter.emit({ |
| 104 | + type: 'tabPress', |
| 105 | + target: route.key, |
| 106 | + canPreventDefault: true, |
| 107 | + data: { isAlreadyFocused: isFocused }, |
| 108 | + }); |
| 109 | + |
| 110 | + if (!isFocused && !event.defaultPrevented) { |
| 111 | + actions.navigate(route.name, route.params); |
| 112 | + } |
| 113 | + }} |
| 114 | + style={{ flex: 1 }} |
| 115 | + > |
| 116 | + <Text>{descriptors[route.key].options.title ?? route.name}</Text> |
| 117 | + </Pressable> |
| 118 | + ))} |
| 119 | + </View> |
| 120 | + <View style={[{ flex: 1 }, contentStyle]}> |
| 121 | + {state.routes.map((route, i) => { |
| 122 | + return ( |
| 123 | + <View |
| 124 | + key={route.key} |
| 125 | + style={[ |
| 126 | + StyleSheet.absoluteFill, |
| 127 | + { display: i === state.index ? 'flex' : 'none' }, |
| 128 | + ]} |
| 129 | + > |
| 130 | + {descriptors[route.key].render()} |
| 131 | + </View> |
| 132 | + ); |
| 133 | + })} |
| 134 | + </View> |
| 135 | + </View> |
| 136 | + ); |
| 137 | +}); |
| 138 | +``` |
| 139 | + |
| 140 | +The `createStandardNavigator` function accepts three generic arguments: |
| 141 | + |
| 142 | +- `MyTabOptions` - The type of the options available for each screen. |
| 143 | +- `MyTabEventMap` - The type of the events that can be emitted by the navigator. |
| 144 | +- `MyTabNavigatorProps` - The type of any additional props accepted by the navigator. |
| 145 | + |
| 146 | +The callback receives `state`, `descriptors`, `actions`, and `emitter` from the navigation library integration: |
| 147 | + |
| 148 | +- `state.routes` contains `{ key, name, params, href }` objects. |
| 149 | +- `descriptors[route.key].options` contains the screen options. |
| 150 | +- `descriptors[route.key].render()` renders the screen. |
| 151 | +- `actions.navigate(name, params)` and `actions.back()` perform navigation. |
| 152 | +- `emitter.emit(...)` emits navigator events to screen listeners. |
| 153 | + |
| 154 | +:::note |
| 155 | + |
| 156 | +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. |
| 157 | + |
| 158 | +::: |
| 159 | + |
| 160 | +## React Navigation entry point |
| 161 | + |
| 162 | +The React Navigation entry point should wrap the standard navigator with `createStandardNavigationFactories` from `@react-navigation/native`: |
| 163 | + |
| 164 | +```tsx title="src/react-navigation.tsx" |
| 165 | +import { |
| 166 | + createStandardNavigationFactories, |
| 167 | + type NavigationProp, |
| 168 | + type ParamListBase, |
| 169 | + type RouteProp, |
| 170 | + type StandardNavigationTypeBagBase, |
| 171 | + type TabActionHelpers, |
| 172 | + type TabNavigationState, |
| 173 | + TabRouter, |
| 174 | + type TabRouterOptions, |
| 175 | +} from '@react-navigation/native'; |
| 176 | + |
| 177 | +import { |
| 178 | + MyTabNavigator, |
| 179 | + type MyTabEventMap, |
| 180 | + type MyTabNavigatorProps, |
| 181 | + type MyTabOptions, |
| 182 | +} from './MyTabNavigator'; |
| 183 | + |
| 184 | +export type MyTabNavigationProp< |
| 185 | + ParamList extends ParamListBase, |
| 186 | + RouteName extends keyof ParamList = keyof ParamList, |
| 187 | + NavigatorID extends string | undefined = undefined, |
| 188 | +> = NavigationProp< |
| 189 | + ParamList, |
| 190 | + RouteName, |
| 191 | + NavigatorID, |
| 192 | + TabNavigationState<ParamList>, |
| 193 | + MyTabOptions, |
| 194 | + MyTabEventMap |
| 195 | +> & |
| 196 | + TabActionHelpers<ParamList>; |
| 197 | + |
| 198 | +export type MyTabScreenProps< |
| 199 | + ParamList extends ParamListBase, |
| 200 | + RouteName extends keyof ParamList = keyof ParamList, |
| 201 | + NavigatorID extends string | undefined = undefined, |
| 202 | +> = { |
| 203 | + navigation: MyTabNavigationProp<ParamList, RouteName, NavigatorID>; |
| 204 | + route: RouteProp<ParamList, RouteName>; |
| 205 | +}; |
| 206 | + |
| 207 | +export interface MyTabTypeBag extends StandardNavigationTypeBagBase { |
| 208 | + State: TabNavigationState<this['ParamList']>; |
| 209 | + ActionHelpers: TabActionHelpers<this['ParamList']>; |
| 210 | + ScreenOptions: MyTabOptions; |
| 211 | + EventMap: MyTabEventMap; |
| 212 | + RouterOptions: TabRouterOptions; |
| 213 | +} |
| 214 | + |
| 215 | +export const { |
| 216 | + createNavigator: createMyTabNavigator, |
| 217 | + createScreen: createMyTabScreen, |
| 218 | +} = createStandardNavigationFactories<MyTabTypeBag, MyTabNavigatorProps>( |
| 219 | + MyTabNavigator, |
| 220 | + TabRouter |
| 221 | +); |
| 222 | +``` |
| 223 | + |
| 224 | +The `createStandardNavigationFactories` function accepts two generic arguments: |
| 225 | + |
| 226 | +- The type bag for the navigator (e.g. `MyTabTypeBag`), which includes the state, action helpers, screen options, event map, and router options types. |
| 227 | +- The type of any additional props accepted by the navigator (e.g. `MyTabNavigatorProps`). |
| 228 | + |
| 229 | +It accepts 3 arguments: |
| 230 | + |
| 231 | +- The standard navigator component. |
| 232 | +- The router factory function from React Navigation (e.g. `TabRouter`, `StackRouter`, etc.). |
| 233 | +- 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. |
| 234 | + |
| 235 | +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. |
| 236 | + |
| 237 | +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. |
| 238 | + |
| 239 | +Consumers can then use the React Navigation entry point: |
| 240 | + |
| 241 | +```tsx static2dynamic |
| 242 | +import { createStaticNavigation } from '@react-navigation/native'; |
| 243 | +import { |
| 244 | + createMyTabNavigator, |
| 245 | + createMyTabScreen, |
| 246 | +} from 'my-navigator/react-navigation'; |
| 247 | + |
| 248 | +const MyTabs = createMyTabNavigator({ |
| 249 | + screens: { |
| 250 | + Home: createMyTabScreen({ |
| 251 | + screen: HomeScreen, |
| 252 | + options: { title: 'Home' }, |
| 253 | + }), |
| 254 | + Feed: createMyTabScreen({ |
| 255 | + screen: FeedScreen, |
| 256 | + options: { title: 'Feed' }, |
| 257 | + }), |
| 258 | + }, |
| 259 | +}); |
| 260 | + |
| 261 | +const Navigation = createStaticNavigation(MyTabs); |
| 262 | +``` |
| 263 | + |
| 264 | +## Expo Router entry point |
| 265 | + |
| 266 | +Work in progress. |
0 commit comments