Skip to content

Commit 363eb6b

Browse files
committed
Document createScreenFactory for 7.x
1 parent 6518235 commit 363eb6b

5 files changed

Lines changed: 201 additions & 58 deletions

File tree

versioned_docs/version-7.x/custom-navigators.md

Lines changed: 78 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ A very basic example looks like this:
2222
import {
2323
useNavigationBuilder,
2424
createNavigatorFactory,
25+
createScreenFactory,
2526
StackRouter,
2627
} from '@react-navigation/native';
2728

@@ -38,6 +39,8 @@ function MyNavigator(props) {
3839
}
3940

4041
export const createMyNavigator = createNavigatorFactory(MyNavigator);
42+
43+
export const createMyScreen = createScreenFactory();
4144
```
4245

4346
Now, we have an already working navigator, even though it doesn't do anything special yet.
@@ -202,6 +205,50 @@ function App() {
202205
}
203206
```
204207
208+
### `createScreenFactory`
209+
210+
This `createScreenFactory` function is used to create a typed screen factory function, which takes a screen configuration object for proper type-checking in static configuration.
211+
212+
Custom navigators should use `createScreenFactory` with appropriate types to create the screen factory function and export it.
213+
214+
Example:
215+
216+
```js
217+
import { createScreenFactory } from '@react-navigation/native';
218+
219+
// ...
220+
221+
export const createMyScreen = createScreenFactory();
222+
```
223+
224+
The [Type-checking navigators](#type-checking-navigators) section covers an example of how the API is used with types.
225+
226+
Then it can be used like this:
227+
228+
```js static2dynamic
229+
import { createStaticNavigation } from '@react-navigation/native';
230+
import { createMyNavigator, createMyScreen } from './myNavigator';
231+
232+
const MyTabs = createMyNavigator({
233+
screens: {
234+
Home: HomeScreen,
235+
Feed: createMyScreen({
236+
screen: FeedScreen,
237+
linking: 'feed/:sort',
238+
options: ({ navigation, route }) => ({
239+
title: `Feed - ${route.params.sort}`,
240+
}),
241+
}),
242+
},
243+
});
244+
245+
const Navigation = createStaticNavigation(MyTabs);
246+
247+
function App() {
248+
return <Navigation />;
249+
}
250+
```
251+
205252
## Type-checking navigators
206253
207254
To type-check navigators, we need to provide few types:
@@ -212,7 +259,7 @@ To type-check navigators, we need to provide few types:
212259
- The type of the navigation object for each screen
213260
- The type of the props for each screen
214261
215-
We also need to export a function to create the navigator configuration with proper types.
262+
We also need to export functions to create the navigator and screen configurations with proper types.
216263
217264
For example, to type-check our custom tab navigator, we can do something like this:
218265
@@ -228,6 +275,7 @@ import {
228275
} from 'react-native';
229276
import {
230277
createNavigatorFactory,
278+
createScreenFactory,
231279
CommonActions,
232280
type DefaultNavigatorOptions,
233281
type NavigatorTypeBagBase,
@@ -362,29 +410,40 @@ function TabNavigator({ tabBarStyle, contentStyle, ...rest }: Props) {
362410
);
363411
}
364412

413+
// Type bag used for type-checking the navigator
414+
export type MyTabTypeBag<
415+
ParamList extends ParamListBase = ParamListBase,
416+
NavigatorID extends string | undefined = string | undefined,
417+
> = {
418+
ParamList: ParamList;
419+
NavigatorID: NavigatorID;
420+
State: TabNavigationState<ParamList>;
421+
ScreenOptions: MyNavigationOptions;
422+
EventMap: MyNavigationEventMap;
423+
NavigationList: {
424+
[RouteName in keyof ParamList]: MyNavigationProp<
425+
ParamList,
426+
RouteName,
427+
NavigatorID
428+
>;
429+
};
430+
Navigator: typeof TabNavigator;
431+
};
432+
365433
// The factory function with overloads for static and dynamic configuration
366434
export function createMyNavigator<
367435
const ParamList extends ParamListBase,
368436
const NavigatorID extends string | undefined = string | undefined,
369-
const TypeBag extends NavigatorTypeBagBase = {
370-
ParamList: ParamList;
371-
NavigatorID: NavigatorID;
372-
State: TabNavigationState<ParamList>;
373-
ScreenOptions: MyNavigationOptions;
374-
EventMap: MyNavigationEventMap;
375-
NavigationList: {
376-
[RouteName in keyof ParamList]: MyNavigationProp<
377-
ParamList,
378-
RouteName,
379-
NavigatorID
380-
>;
381-
};
382-
Navigator: typeof TabNavigator;
383-
},
437+
const TypeBag extends NavigatorTypeBagBase = MyTabTypeBag<
438+
ParamList,
439+
NavigatorID
440+
>,
384441
const Config extends StaticConfig<TypeBag> = StaticConfig<TypeBag>,
385442
>(config?: Config): TypedNavigator<TypeBag, Config> {
386443
return createNavigatorFactory(TabNavigator)(config);
387444
}
445+
446+
export const createMyScreen = createScreenFactory<MyTabTypeBag>();
388447
```
389448
390449
## Extending Navigators
@@ -396,6 +455,7 @@ import * as React from 'react';
396455
import {
397456
useNavigationBuilder,
398457
createNavigatorFactory,
458+
createScreenFactory,
399459
TabRouter,
400460
} from '@react-navigation/native';
401461
import { BottomTabView } from '@react-navigation/bottom-tabs';
@@ -442,6 +502,8 @@ function MyBottomTabNavigator({
442502
export function createMyNavigator(config) {
443503
return createNavigatorFactory(MyBottomTabNavigator)(config);
444504
}
505+
506+
export const createMyBottomTabScreen = createScreenFactory();
445507
```
446508
447509
Now, we can customize it to add additional functionality or change the behavior. For example, use a [custom router](custom-routers.md) instead of the default [`TabRouter`](custom-routers.md#built-in-routers):

versioned_docs/version-7.x/static-configuration.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,42 @@ const RootStack = createNativeStackNavigator({
121121

122122
The configuration object for a screen accepts the [properties described in the Screen page](screen.md). In addition, the following properties are available when using static configuration:
123123

124+
#### `createXScreen`
125+
126+
Each navigator exports a helper function to create screen configurations with proper TypeScript types. These helpers enable type inference for the params in the configuration callbacks such as `options` and `listeners`.
127+
128+
Example usage:
129+
130+
```js
131+
import {
132+
createNativeStackNavigator,
133+
createNativeStackScreen,
134+
} from '@react-navigation/native-stack';
135+
136+
const Stack = createNativeStackNavigator({
137+
screens: {
138+
Profile: createNativeStackScreen({
139+
screen: ProfileScreen,
140+
options: ({ route }) => {
141+
const userId = route.params.userId;
142+
143+
return {
144+
title: `${userId}'s profile`,
145+
};
146+
},
147+
}),
148+
},
149+
});
150+
```
151+
152+
Each navigator exports its own helper function:
153+
154+
- `createNativeStackScreen` from `@react-navigation/native-stack`
155+
- `createStackScreen` from `@react-navigation/stack`
156+
- `createBottomTabScreen` from `@react-navigation/bottom-tabs`
157+
- `createDrawerScreen` from `@react-navigation/drawer`
158+
- `createMaterialTopTabScreen` from `@react-navigation/material-top-tabs`
159+
124160
#### `linking`
125161

126162
[Linking configuration](configuring-links.md) for the screen. It can be either a string for a path or an object with the linking configuration:

versioned_docs/version-7.x/static-vs-dynamic.md

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,14 @@ All props passed to `<Stack.Screen>` except `name` and `component` become proper
147147
```
148148

149149
```js title="Static API"
150+
import {
151+
createNativeStackNavigator,
152+
createNativeStackScreen,
153+
} from '@react-navigation/native-stack';
154+
150155
const RootStack = createNativeStackNavigator({
151156
screens: {
152-
Profile: {
157+
Profile: createNativeStackScreen({
153158
screen: ProfileScreen,
154159
options: ({ route }) => ({
155160
title: route.params.userId,
@@ -158,11 +163,13 @@ const RootStack = createNativeStackNavigator({
158163
focus: () => console.log('focused'),
159164
},
160165
getId: ({ params }) => params.userId,
161-
},
166+
}),
162167
},
163168
});
164169
```
165170

171+
The [`createXScreen`](static-configuration.md#createxscreen) helper is for type inference in `options` and `listeners` callbacks. Each navigator exports its own screen helper: [`createNativeStackScreen`](native-stack-navigator.md), [`createStackScreen`](stack-navigator.md), [`createBottomTabScreen`](bottom-tab-navigator.md), [`createDrawerScreen`](drawer-navigator.md), [`createMaterialTopTabScreen`](material-top-tab-navigator.md).
172+
166173
## Conditional screens
167174

168175
In the dynamic API, screens can be conditionally defined by rendering `Screen` components conditionally. In the static API, the [`if`](static-configuration.md#if) property can be used to conditionally render screens based on a hook returning a boolean.
@@ -590,6 +597,10 @@ function ProfileScreen({ route }: ProfileScreenProps) {
590597

591598
```ts title="Static API"
592599
import type { StaticScreenProps } from '@react-navigation/native';
600+
import {
601+
createNativeStackNavigator,
602+
createNativeStackScreen,
603+
} from '@react-navigation/native-stack';
593604

594605
type Props = StaticScreenProps<{ userId: string }>;
595606

@@ -599,7 +610,9 @@ function ProfileScreen({ route }: Props) {
599610

600611
const RootStack = createNativeStackNavigator({
601612
screens: {
602-
Profile: ProfileScreen,
613+
Profile: createNativeStackScreen({
614+
screen: ProfileScreen,
615+
}),
603616
},
604617
});
605618

versioned_docs/version-7.x/typescript.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,36 @@ There are 2 steps to configure TypeScript with the static API:
6363

6464
This is needed to type-check the [`useNavigation`](use-navigation.md) hook.
6565

66+
The `createXScreen` helper functions enable type inference in screen configuration callbacks like `options`, `listeners`, etc. Each navigator exports its own version of the helper function:
67+
68+
- `createNativeStackScreen` from `@react-navigation/native-stack`
69+
- `createStackScreen` from `@react-navigation/stack`
70+
- `createBottomTabScreen` from `@react-navigation/bottom-tabs`
71+
- `createDrawerScreen` from `@react-navigation/drawer`
72+
- `createMaterialTopTabScreen` from `@react-navigation/material-top-tabs`
73+
74+
For example:
75+
76+
```ts
77+
import {
78+
createNativeStackNavigator,
79+
createNativeStackScreen,
80+
} from '@react-navigation/native-stack';
81+
82+
const RootStack = createNativeStackNavigator({
83+
screens: {
84+
Profile: createNativeStackScreen({
85+
screen: ProfileScreen,
86+
options: ({ route }) => ({
87+
title: route.params.username,
88+
}),
89+
}),
90+
},
91+
});
92+
```
93+
94+
See [Static configuration](static-configuration.md#createxscreen) for more details.
95+
6696
## Navigator specific types
6797

6898
Generally, we recommend using the default types for the [`useNavigation`](use-navigation.md) prop to access the navigation object in a navigator-agnostic manner. However, if you need to use navigator-specific APIs, e.g. `setOptions` to update navigator options, `push`, `pop`, `popTo` etc. with stacks, `openDrawer`, `closeDrawer` etc. with drawer and so on, you need to manually annotate [`useNavigation`](use-navigation.md):

versioned_docs/version-8.x/upgrading-from-7.x.md

Lines changed: 41 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -136,45 +136,6 @@ If you're using dynamic configuration, you can use `as`:
136136
+ const focusedRouteName = useNavigationState((state) => state.routes[state.index].name as keyof RootStackParamList);
137137
```
138138

139-
#### New `createXScreen` API for creating screen config
140-
141-
One of the limitations of the static config API is that the type of `route` object can't be inferred in screen callback, listeners callback etc. This made it difficult to use route params in these callbacks.
142-
143-
To address this, we added a new `createXScreen` API for each navigator to create screen config with proper types:
144-
145-
```diff lang=js
146-
const Stack = createStackNavigator({
147-
screens: {
148-
- Profile: {
149-
- screen: ProfileScreen,
150-
- options: ({ route }) => {
151-
- const userId = route.params.userId; // Don't know the type of route params
152-
-
153-
- return { title: `User ${userId}` };
154-
- },
155-
- },
156-
+ Profile: createStackScreen({
157-
+ screen: ProfileScreen,
158-
+ options: ({ route }) => {
159-
+ const userId = route.params.userId; // Now correctly inferred
160-
+
161-
+ return { title: `User ${userId}` };
162-
+ },
163-
+ });
164-
}
165-
});
166-
```
167-
168-
When using the `createXScreen` API, the type of params are automatically inferred based on the type annotation for the component specified in `screen` (e.g. `(props: StaticScreenProps<ProfileParams>)`) and the path pattern specified in the linking config (e.g. `linking: 'profile/:userId'`).
169-
170-
Each navigator exports its own helper function, e.g. `createNativeStackScreen` for Native Stack Navigator, `createBottomTabScreen` for Bottom Tab Navigator, `createDrawerScreen` for Drawer Navigator etc.
171-
172-
:::note
173-
174-
This is technically not a breaking change. It's not required to use this API and your existing code will continue to work as before. You can incrementally adopt this API for new screens to get proper types for `route` object in various callbacks such as `options`, `listeners`, etc.
175-
176-
:::
177-
178139
See [Static configuration docs](static-configuration.md#createxscreen) for more details.
179140

180141
#### Custom navigators have a simpler type API
@@ -1040,6 +1001,47 @@ See the [`server rendering guide`](server-rendering.md) for a detailed guide and
10401001

10411002
## New features
10421003

1004+
### `createXScreen` let's you create screen config with proper types
1005+
1006+
One of the limitations of the static config API is that the type of `route` object can't be inferred in screen callback, listeners callback etc. This made it difficult to use route params in these callbacks.
1007+
1008+
To address this, we added a new `createXScreen` API for each navigator to create screen config with proper types:
1009+
1010+
```diff lang=js
1011+
const Stack = createStackNavigator({
1012+
screens: {
1013+
- Profile: {
1014+
- screen: ProfileScreen,
1015+
- options: ({ route }) => {
1016+
- const userId = route.params.userId; // Don't know the type of route params
1017+
-
1018+
- return { title: `User ${userId}` };
1019+
- },
1020+
- },
1021+
+ Profile: createStackScreen({
1022+
+ screen: ProfileScreen,
1023+
+ options: ({ route }) => {
1024+
+ const userId = route.params.userId; // Now correctly inferred
1025+
+
1026+
+ return { title: `User ${userId}` };
1027+
+ },
1028+
+ });
1029+
}
1030+
});
1031+
```
1032+
1033+
When using the `createXScreen` API, the type of params are automatically inferred based on the type annotation for the component specified in `screen` (e.g. `(props: StaticScreenProps<ProfileParams>)`) and the path pattern specified in the linking config (e.g. `linking: 'profile/:userId'`).
1034+
1035+
Each navigator exports its own helper function, e.g. `createNativeStackScreen` for Native Stack Navigator, `createBottomTabScreen` for Bottom Tab Navigator, `createDrawerScreen` for Drawer Navigator etc.
1036+
1037+
This API has also been backported to React Navigation 7.
1038+
1039+
:::note
1040+
1041+
It's not required to use this API and your existing code will continue to work as before. You can incrementally adopt this API to get proper types for `route` object in various callbacks such as `options`, `listeners`, etc.
1042+
1043+
:::
1044+
10431045
### Common hooks now accept name of the screen
10441046

10451047
The `useNavigation`, `useRoute`, and `useNavigationState` hooks can now optionally accept the name of the screen:

0 commit comments

Comments
 (0)