@rn-tools/navigation 2.3.0 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -13
- package/readme.md +47 -757
- package/src/index.ts +2 -2
- package/src/navigation-client.test.tsx +206 -0
- package/src/navigation-client.tsx +216 -0
- package/src/navigation.tsx +38 -147
- package/src/stack.test.tsx +341 -0
- package/src/stack.tsx +78 -301
- package/src/tabs.test.tsx +288 -0
- package/src/tabs.tsx +142 -286
- package/src/__tests__/navigation-reducer.test.tsx +0 -346
- package/src/__tests__/stack.test.tsx +0 -48
- package/src/contexts.tsx +0 -8
- package/src/deep-links.tsx +0 -37
- package/src/navigation-reducer.ts +0 -487
- package/src/navigation-store.ts +0 -58
- package/src/types.ts +0 -48
- package/src/utils.ts +0 -40
package/readme.md
CHANGED
|
@@ -1,27 +1,6 @@
|
|
|
1
1
|
# @rn-tools/navigation
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Table of Contents
|
|
6
|
-
|
|
7
|
-
- [Installation](#installation)
|
|
8
|
-
- [Basic Usage](#basic-usage)
|
|
9
|
-
- [Stack Navigator](#stack-navigator)
|
|
10
|
-
- [Tab Navigator](#tab-navigator)
|
|
11
|
-
- [Rendering a stack inside of a tabbed screen](#rendering-a-stack-inside-of-a-tabbed-screen)
|
|
12
|
-
- [Targeting a specific stack](#targeting-a-specific-stack)
|
|
13
|
-
- [Pushing a screen once](#pushing-a-screen-once)
|
|
14
|
-
- [Targeting specific tabs](#targeting-specific-tabs)
|
|
15
|
-
- [Rendering a header](#rendering-a-header)
|
|
16
|
-
- [Configuring screen props](#configuring-screen-props)
|
|
17
|
-
- [Components](#components)
|
|
18
|
-
- [Stack](#stack)
|
|
19
|
-
- [Tabs](#tabs)
|
|
20
|
-
- [Guides](#guides)
|
|
21
|
-
- [Authentication](#authentication)
|
|
22
|
-
- [Deep Links](#deep-links)
|
|
23
|
-
- [Preventing going back](#preventing-going-back)
|
|
24
|
-
- [Testing](#testing)
|
|
3
|
+
Navigation primitives for React Native. Built on `react-native-screens`.
|
|
25
4
|
|
|
26
5
|
## Installation
|
|
27
6
|
|
|
@@ -29,750 +8,61 @@ A set of useful navigation components for React Native. Built with `react-native
|
|
|
29
8
|
yarn expo install @rn-tools/navigation react-native-screens
|
|
30
9
|
```
|
|
31
10
|
|
|
32
|
-
##
|
|
33
|
-
|
|
34
|
-
For basic usage, the exported `Stack.Navigator` and `Tabs.Navigator` will get you up and running quickly.
|
|
35
|
-
|
|
36
|
-
The [Guides](#guides) section covers how to use lower-level `Stack` and `Tabs` components in a variety of navigation patterns.
|
|
37
|
-
|
|
38
|
-
`Stack` and `Tabs` are composable components that can be safely nested within each other without any additional configuration or setup.
|
|
39
|
-
|
|
40
|
-
### Stack Navigator
|
|
41
|
-
|
|
42
|
-
The `Stack.Navigator` component manages screens. Under the hood this is using `react-native-screens` to handle pushing and popping natively.
|
|
43
|
-
|
|
44
|
-
Screens are pushed and popped by the exported navigation methods:
|
|
45
|
-
|
|
46
|
-
- `navigation.pushScreen(screenElement: React.ReactElement<ScreenProps>, options?: PushScreenOptions) => void`
|
|
47
|
-
|
|
48
|
-
- `navigation.popScreen(numberOfScreens: number) => void`
|
|
49
|
-
|
|
50
|
-
In the majority of cases, these methods will determine the right stack without you needing to specify. But you can target a specific stacks as well if you need to! This is covered in the [Targeting a specific stack](#targeting-a-specific-stack) section.
|
|
51
|
-
|
|
52
|
-
```tsx
|
|
53
|
-
import { Stack, navigation } from "@rn-tools/navigation";
|
|
54
|
-
import * as React from "react";
|
|
55
|
-
import { View, Text, Button } from "react-native";
|
|
56
|
-
|
|
57
|
-
export function BasicStack() {
|
|
58
|
-
return <Stack.Navigator rootScreen={<MyScreen title="Root Screen" />} />;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function MyScreen({
|
|
62
|
-
title,
|
|
63
|
-
children,
|
|
64
|
-
}: {
|
|
65
|
-
title: string;
|
|
66
|
-
children?: React.ReactNode;
|
|
67
|
-
}) {
|
|
68
|
-
function pushScreen() {
|
|
69
|
-
navigation.pushScreen(
|
|
70
|
-
<Stack.Screen>
|
|
71
|
-
<MyScreen title="Pushed screen">
|
|
72
|
-
<Button title="Pop screen" onPress={popScreen} />
|
|
73
|
-
</MyScreen>
|
|
74
|
-
</Stack.Screen>
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function popScreen() {
|
|
79
|
-
navigation.popScreen();
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return (
|
|
83
|
-
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
|
|
84
|
-
<Text>{title}</Text>
|
|
85
|
-
<Button title="Push screen" onPress={pushScreen} />
|
|
86
|
-
{children}
|
|
87
|
-
</View>
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
**Note**: The components passed to `navigation.pushScreen` need to be wrapped in a `Stack.Screen`. Create a wrapper to simplify your usage if you'd like:
|
|
93
|
-
|
|
94
|
-
```tsx
|
|
95
|
-
function myPushScreen(
|
|
96
|
-
element: React.ReactElement<unknown>,
|
|
97
|
-
options?: PushScreenOptions
|
|
98
|
-
) {
|
|
99
|
-
navigation.pushScreen(<Stack.Screen>{element}</Stack.Screen>, options);
|
|
100
|
-
}
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
### Tab Navigator
|
|
104
|
-
|
|
105
|
-
The `Tabs.Navigator` component also uses `react-native-screens` to handle switching between tabs natively.
|
|
106
|
-
|
|
107
|
-
The active tab can be changed via the `navigation.setTabIndex` method, however the built in tabbar handles switching between screens out of the box.
|
|
108
|
-
|
|
109
|
-
```tsx
|
|
110
|
-
import { Tabs, navigation, Stack } from "@rn-tools/navigation";
|
|
111
|
-
import * as React from "react";
|
|
112
|
-
import { View, Text, Button } from "react-native";
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
export function BasicTabs() {
|
|
116
|
-
return (
|
|
117
|
-
<Stack.Navigator rootScreen={<MyTabs />} />
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function MyTabs() {
|
|
122
|
-
return (
|
|
123
|
-
<Tabs.Navigator
|
|
124
|
-
tabbarPosition="bottom"
|
|
125
|
-
tabbarStyle={{ backgroundColor: "blue" }}
|
|
126
|
-
screens={[
|
|
127
|
-
{
|
|
128
|
-
key: "1",
|
|
129
|
-
screen: <MyScreen title="Screen 1" bg="red" />,
|
|
130
|
-
tab: ({ isActive }) => <MyTab isActive={isActive}>1</MyTab>,
|
|
131
|
-
},
|
|
132
|
-
{
|
|
133
|
-
key: "2",
|
|
134
|
-
screen: <MyScreen title="Screen 2" bg="blue" />,
|
|
135
|
-
tab: ({ isActive }) => <MyTab isActive={isActive}>2</MyTab>,
|
|
136
|
-
},
|
|
137
|
-
{
|
|
138
|
-
key: "3",
|
|
139
|
-
screen: <MyScreen title="Screen 3" bg="purple" />,
|
|
140
|
-
tab: ({ isActive }) => <MyTab isActive={isActive}>3</MyTab>,
|
|
141
|
-
},
|
|
142
|
-
]}
|
|
143
|
-
/>
|
|
144
|
-
);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function MyTab({
|
|
148
|
-
children,
|
|
149
|
-
isActive,
|
|
150
|
-
}: {
|
|
151
|
-
children: React.ReactNode;
|
|
152
|
-
isActive: boolean;
|
|
153
|
-
}) {
|
|
154
|
-
return (
|
|
155
|
-
<View style={{ padding: 16, alignItems: "center" }}>
|
|
156
|
-
<Text
|
|
157
|
-
style={isActive ? { fontWeight: "bold" } : { fontWeight: "normal" }}
|
|
158
|
-
>
|
|
159
|
-
{children}
|
|
160
|
-
</Text>
|
|
161
|
-
</View>
|
|
162
|
-
);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function MyScreen({
|
|
166
|
-
title,
|
|
167
|
-
children,
|
|
168
|
-
bg,
|
|
169
|
-
}: {
|
|
170
|
-
title: string;
|
|
171
|
-
children?: React.ReactNode;
|
|
172
|
-
bg?: string;
|
|
173
|
-
}) {
|
|
174
|
-
function pushScreen() {
|
|
175
|
-
navigation.pushScreen(
|
|
176
|
-
<Stack.Screen>
|
|
177
|
-
<MyScreen title="Pushed screen" bg={bg}>
|
|
178
|
-
<Button title="Pop screen" onPress={popScreen} />
|
|
179
|
-
</MyScreen>
|
|
180
|
-
</Stack.Screen>
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function popScreen() {
|
|
185
|
-
navigation.popScreen();
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
return (
|
|
189
|
-
<View
|
|
190
|
-
style={{
|
|
191
|
-
flex: 1,
|
|
192
|
-
justifyContent: "center",
|
|
193
|
-
alignItems: "center",
|
|
194
|
-
backgroundColor: bg || "white",
|
|
195
|
-
}}
|
|
196
|
-
>
|
|
197
|
-
<Text>{title}</Text>
|
|
198
|
-
<Button title="Push screen" onPress={pushScreen} />
|
|
199
|
-
{children}
|
|
200
|
-
</View>
|
|
201
|
-
);
|
|
202
|
-
}
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
### Rendering a stack inside of a tabbed screen
|
|
206
|
-
|
|
207
|
-
Each tab can have its own stack by nesting the `Stack.Navigator` component.
|
|
208
|
-
|
|
209
|
-
```tsx
|
|
210
|
-
function MyTabs() {
|
|
211
|
-
return (
|
|
212
|
-
<Tabs.Navigator
|
|
213
|
-
screens={[
|
|
214
|
-
{
|
|
215
|
-
key: "1",
|
|
216
|
-
// Wrap the screen in a Stack.Navigator:
|
|
217
|
-
screen: (
|
|
218
|
-
<Stack.Navigator
|
|
219
|
-
rootScreen={<MyScreen title="Screen 1" bg="red" />}
|
|
220
|
-
/>
|
|
221
|
-
),
|
|
222
|
-
tab: ({ isActive }) => <MyTab isActive={isActive}>1</MyTab>,
|
|
223
|
-
},
|
|
224
|
-
// ...other screens
|
|
225
|
-
]}
|
|
226
|
-
/>
|
|
227
|
-
);
|
|
228
|
-
}
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
### Targeting a specific stack
|
|
232
|
-
|
|
233
|
-
Provide an `id` prop to a stack and target when pushing the screen.
|
|
234
|
-
|
|
235
|
-
```tsx
|
|
236
|
-
let MAIN_STACK_ID = "mainStack";
|
|
237
|
-
|
|
238
|
-
function App() {
|
|
239
|
-
return (
|
|
240
|
-
<Stack.Navigator
|
|
241
|
-
id={MAIN_STACK_ID}
|
|
242
|
-
rootScreen={<MyScreen title="Root Screen" />}
|
|
243
|
-
/>
|
|
244
|
-
);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
function pushToMainStack(
|
|
248
|
-
screenElement: React.ReactElement<unknown>,
|
|
249
|
-
options: PushScreenOptions
|
|
250
|
-
) {
|
|
251
|
-
navigation.pushScreen(<Stack.Screen>{screenElement}</Stack.Screen>, {
|
|
252
|
-
...options,
|
|
253
|
-
stackId: MAIN_STACK_ID,
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
### Pushing a screen once
|
|
259
|
-
|
|
260
|
-
Provide a `screenId` option to only push the screen once. Screen ids are unique across all stacks.
|
|
261
|
-
|
|
262
|
-
```tsx
|
|
263
|
-
function pushThisScreenOnce() {
|
|
264
|
-
navigation.pushScreen(
|
|
265
|
-
<Stack.Screen>
|
|
266
|
-
<MyScreen title="Pushed screen" />
|
|
267
|
-
</Stack.Screen>,
|
|
268
|
-
{
|
|
269
|
-
// This screen will only be pushed once
|
|
270
|
-
screenId: "unique-key",
|
|
271
|
-
}
|
|
272
|
-
);
|
|
273
|
-
}
|
|
274
|
-
```
|
|
275
|
-
|
|
276
|
-
### Targeting specific tabs
|
|
277
|
-
|
|
278
|
-
Similar to `Stack.Navigator`, pass an `id` prop to a `Tabs.Navigator` and target a navigator expliclity when setting the active tab.
|
|
279
|
-
|
|
280
|
-
```tsx
|
|
281
|
-
let MAIN_TAB_ID = "mainTabs";
|
|
282
|
-
|
|
283
|
-
function App() {
|
|
284
|
-
return <Tabs.Navigator id={MAIN_TAB_ID} screens={tabs} />;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
function switchMainTabsToTab(tabIndex: number) {
|
|
288
|
-
navigation.setTabIndex(tabIndex, { tabId: MAIN_TAB_ID });
|
|
289
|
-
}
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
### Rendering a header
|
|
293
|
-
|
|
294
|
-
Use the `Stack.Header` component to render a native header in a screen by passing it as a prop to `Stack.Screen`.
|
|
295
|
-
|
|
296
|
-
Under the hood this is using `react-native-screens` header - [here is a reference for the available props](https://github.com/software-mansion/react-native-screens/blob/main/guides/GUIDE_FOR_LIBRARY_AUTHORS.md#screenstackheaderconfig)
|
|
297
|
-
|
|
298
|
-
You can provide custom left, center, and right views in the header by using the `Stack.HeaderLeft`, `Stack.HeaderCenter`, and `Stack.HeaderRight` view container components as children of `Stack.Header`.
|
|
299
|
-
|
|
300
|
-
```tsx
|
|
301
|
-
import { navigation, Stack } from "@rn-tools/navigation";
|
|
302
|
-
import * as React from "react";
|
|
303
|
-
import { Button, View, TextInput, Text } from "react-native";
|
|
304
|
-
|
|
305
|
-
export function HeaderExample() {
|
|
306
|
-
return (
|
|
307
|
-
<View>
|
|
308
|
-
<Button
|
|
309
|
-
title="Push screen with header"
|
|
310
|
-
onPress={() => navigation.pushScreen(<MyScreenWithHeader />)}
|
|
311
|
-
/>
|
|
312
|
-
</View>
|
|
313
|
-
);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
function MyScreenWithHeader() {
|
|
317
|
-
let [title, setTitle] = React.useState("");
|
|
318
|
-
|
|
319
|
-
return (
|
|
320
|
-
<Stack.Screen
|
|
321
|
-
header={
|
|
322
|
-
<Stack.Header
|
|
323
|
-
title={title}
|
|
324
|
-
backTitle="Custom back title"
|
|
325
|
-
backTitleFontSize={16}
|
|
326
|
-
hideBackButton={false}
|
|
327
|
-
>
|
|
328
|
-
<Stack.HeaderRight>
|
|
329
|
-
<Text>Custom right text!</Text>
|
|
330
|
-
</Stack.HeaderRight>
|
|
331
|
-
</Stack.Header>
|
|
332
|
-
}
|
|
333
|
-
>
|
|
334
|
-
<View
|
|
335
|
-
style={{
|
|
336
|
-
flex: 1,
|
|
337
|
-
alignItems: "center",
|
|
338
|
-
paddingVertical: 48,
|
|
339
|
-
}}
|
|
340
|
-
>
|
|
341
|
-
<TextInput
|
|
342
|
-
style={{ fontSize: 26, fontWeight: "semibold" }}
|
|
343
|
-
value={title}
|
|
344
|
-
onChangeText={setTitle}
|
|
345
|
-
placeholder="Enter header text"
|
|
346
|
-
/>
|
|
347
|
-
</View>
|
|
348
|
-
</Stack.Screen>
|
|
349
|
-
);
|
|
350
|
-
}
|
|
351
|
-
```
|
|
352
|
-
|
|
353
|
-
### Configuring screen props
|
|
354
|
-
|
|
355
|
-
The `Stack.Screen` component is a wrapper around the `Screen` component from `react-native-screens`. [Screen props reference](https://github.com/software-mansion/react-native-screens/blob/main/src/types.tsx#L101)
|
|
356
|
-
|
|
357
|
-
Some notable props are `stackPresentation`, `stackAnimation`, and `gestureEnabled`, however there are many more available.
|
|
11
|
+
## Quick Start
|
|
358
12
|
|
|
359
13
|
```tsx
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
<Stack.Screen>{rootScreen}</Stack.Screen>
|
|
397
|
-
<Stack.Slot />
|
|
398
|
-
</Stack.Screens>
|
|
399
|
-
</Stack.Root>
|
|
400
|
-
);
|
|
401
|
-
}
|
|
402
|
-
```
|
|
403
|
-
|
|
404
|
-
- `Stack.Root` - The root component for a stack navigator.
|
|
405
|
-
- `Stack.Screens` - The container for all screens in a stack.
|
|
406
|
-
- This is a `react-native-screens` StackScreenContainer component under the hood.
|
|
407
|
-
- All UI rendered children should be `Stack.Screen` or `Stack.Slot` components.
|
|
408
|
-
- You can still render contexts and other non-UI components directly under `Stack.Screens`. See the Authentication guide for examples of this
|
|
409
|
-
- `Stack.Screen` - A screen in a stack.
|
|
410
|
-
- This is a `react-native-screens` StackScreen component under the hood.
|
|
411
|
-
- Notable props include `gestureEnabled`, `stackPresentation` and `preventNativeDismiss` to control how the screen can be interacted with.
|
|
412
|
-
- Reference for props that can be passed: [Screen Props](https://github.com/software-mansion/react-native-screens/blob/main/guides/GUIDE_FOR_LIBRARY_AUTHORS.md#screen)
|
|
413
|
-
- `Stack.Slot` - A slot for screens to be pushed into.
|
|
414
|
-
- This component is used to render screens that are pushed using `navigation.pushScreen` - don't forget to render this somewhere in `Stack.Screens`!
|
|
415
|
-
- `Stack.Header` - A header for a screen.
|
|
416
|
-
- **Must be rendered as the first child of a `Stack.Screen` component.**
|
|
417
|
-
- This is a `react-native-screens` StackHeader component under the hood.
|
|
418
|
-
- Reference for props that can be passed: [Header Props](https://github.com/software-mansion/react-native-screens/blob/main/guides/GUIDE_FOR_LIBRARY_AUTHORS.md#screenstackheaderconfig)
|
|
419
|
-
|
|
420
|
-
## Tabs
|
|
421
|
-
|
|
422
|
-
This is the implementation of the exported `Tabs.Navigator` component:
|
|
423
|
-
|
|
424
|
-
```tsx
|
|
425
|
-
export type TabNavigatorProps = Omit<TabsRootProps, "children"> & {
|
|
426
|
-
screens: TabNavigatorScreenOptions[];
|
|
427
|
-
tabbarPosition?: "top" | "bottom";
|
|
428
|
-
tabbarStyle?: ViewProps["style"];
|
|
429
|
-
};
|
|
430
|
-
|
|
431
|
-
export type TabNavigatorScreenOptions = {
|
|
432
|
-
key: string;
|
|
433
|
-
screen: React.ReactElement<unknown>;
|
|
434
|
-
tab: (props: { isActive: boolean; onPress: () => void }) => React.ReactNode;
|
|
435
|
-
};
|
|
436
|
-
|
|
437
|
-
let TabNavigator = React.memo(function TabNavigator({
|
|
438
|
-
screens,
|
|
439
|
-
tabbarPosition = "bottom",
|
|
440
|
-
tabbarStyle: tabbarStyleProp,
|
|
441
|
-
...rootProps
|
|
442
|
-
}: TabNavigatorProps) {
|
|
443
|
-
let insets = useSafeAreaInsetsSafe();
|
|
444
|
-
|
|
445
|
-
let tabbarStyle = React.useMemo(() => {
|
|
446
|
-
return [
|
|
447
|
-
defaultTabbarStyle,
|
|
448
|
-
{
|
|
449
|
-
paddingBottom: tabbarPosition === "bottom" ? insets.bottom : 0,
|
|
450
|
-
paddingTop: tabbarPosition === "top" ? insets.top : 0,
|
|
451
|
-
},
|
|
452
|
-
tabbarStyleProp,
|
|
453
|
-
];
|
|
454
|
-
}, [tabbarPosition, tabbarStyleProp, insets]);
|
|
455
|
-
|
|
456
|
-
return (
|
|
457
|
-
<Tabs.Root {...rootProps}>
|
|
458
|
-
{tabbarPosition === "top" && (
|
|
459
|
-
<Tabs.Tabbar style={tabbarStyle}>
|
|
460
|
-
{screens.map((screen) => {
|
|
461
|
-
return <Tabs.Tab key={screen.key}>{screen.tab}</Tabs.Tab>;
|
|
462
|
-
})}
|
|
463
|
-
</Tabs.Tabbar>
|
|
464
|
-
)}
|
|
465
|
-
|
|
466
|
-
<Tabs.Screens>
|
|
467
|
-
{screens.map((screen) => {
|
|
468
|
-
return <Tabs.Screen key={screen.key}>{screen.screen}</Tabs.Screen>;
|
|
469
|
-
})}
|
|
470
|
-
</Tabs.Screens>
|
|
471
|
-
|
|
472
|
-
{tabbarPosition === "bottom" && (
|
|
473
|
-
<Tabs.Tabbar style={tabbarStyle}>
|
|
474
|
-
{screens.map((screen) => {
|
|
475
|
-
return <Tabs.Tab key={screen.key}>{screen.tab}</Tabs.Tab>;
|
|
476
|
-
})}
|
|
477
|
-
</Tabs.Tabbar>
|
|
478
|
-
)}
|
|
479
|
-
</Tabs.Root>
|
|
480
|
-
);
|
|
481
|
-
});
|
|
482
|
-
```
|
|
483
|
-
|
|
484
|
-
- `Tabs.Root` - The root component for a tabs navigator.
|
|
485
|
-
- `Tabs.Screens` - The container for all screens in a tabs navigator.
|
|
486
|
-
- This is a `react-native-screens` ScreenContainer component under the hood.
|
|
487
|
-
- All UI rendered children should be `Tabs.Screen` components.
|
|
488
|
-
- `Tabs.Screen` - A screen in a tabs navigator.
|
|
489
|
-
- `Tabs.Tabbar` - The tab bar for a tabs navigator.
|
|
490
|
-
- Each child Tab of the tab bar will target the screen that corresponds to its index
|
|
491
|
-
- `Tabs.Tab` - A tab in a tabs navigator
|
|
492
|
-
- This is a Pressable component that switches the active screen
|
|
493
|
-
|
|
494
|
-
## Guides
|
|
495
|
-
|
|
496
|
-
### Authentication
|
|
497
|
-
|
|
498
|
-
For this example, we want to show our main app when the user is logged in, otherwise show the login screen. You can use the `Stack` component to conditionally render screens based on the user's state.
|
|
499
|
-
|
|
500
|
-
```tsx
|
|
501
|
-
import * as React from "react";
|
|
502
|
-
import { Stack } from "@rn-tools/navigation";
|
|
503
|
-
|
|
504
|
-
function App() {
|
|
505
|
-
let [user, setUser] = React.useState(null);
|
|
506
|
-
|
|
507
|
-
return (
|
|
508
|
-
<Stack.Root>
|
|
509
|
-
<Stack.Screens>
|
|
510
|
-
<Stack.Screen>
|
|
511
|
-
<MyLoginScreen onLoginSuccess={(user) => setUser(user)} />
|
|
512
|
-
</Stack.Screen>
|
|
513
|
-
|
|
514
|
-
{user != null && (
|
|
515
|
-
<UserContext.Provider value={user}>
|
|
516
|
-
<Stack.Screen gestureEnabled={false}>
|
|
517
|
-
<MyAuthenticatedApp />
|
|
518
|
-
</Stack.Screen>
|
|
519
|
-
<Stack.Slot />
|
|
520
|
-
</UserContext.Provider>
|
|
521
|
-
)}
|
|
522
|
-
</Stack.Screens>
|
|
523
|
-
</Stack.Root>
|
|
524
|
-
);
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
let UserContext = React.createContext<User | null>(null);
|
|
528
|
-
|
|
529
|
-
let useUser = () => {
|
|
530
|
-
let user = React.useContext(UserContext);
|
|
531
|
-
|
|
532
|
-
if (user == null) {
|
|
533
|
-
throw new Error("User not found");
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
return user;
|
|
537
|
-
};
|
|
538
|
-
```
|
|
539
|
-
|
|
540
|
-
### Deep Links
|
|
541
|
-
|
|
542
|
-
This section will cover how to respond to deep links in your app. Deep links usually have some extra setup required - use Expo's [Deep Linking Guide](https://docs.expo.dev/guides/deep-linking/) to get started. Once you are able to receive deep links, use the `DeepLinks` component exported from this library to handle them.
|
|
543
|
-
|
|
544
|
-
In this example we will have a basic 3 tab view. We want to repond to the link `home/items/:id` by navigating to the home tab and then pushing a detail screen with the corresponding item id. The deep link component takes an array of handlers which are functions that will be invoked when their `path` matches the deep link that was opened.
|
|
545
|
-
|
|
546
|
-
- Only the first matching handler will be invoked.
|
|
547
|
-
- The handler function will receive the params from the deep link - these use the same token syntax as libraries like `react-router` and `express` for path params.
|
|
548
|
-
- Make sure that the `DeepLinks` component is inside of a `Stack` component
|
|
549
|
-
|
|
550
|
-
```tsx
|
|
551
|
-
import { DeepLinks, navigation, Stack, Tabs } from "@rn-tools/navigation";
|
|
552
|
-
import * as Linking from "expo-linking";
|
|
553
|
-
import * as React from "react";
|
|
554
|
-
import { View, Text, TouchableOpacity } from "react-native";
|
|
555
|
-
|
|
556
|
-
export default function DeepLinksExample() {
|
|
557
|
-
// You'll likely want to use Expo's Linking API to get the current URL and path
|
|
558
|
-
// let url = Linking.useURL()
|
|
559
|
-
// let { path } = Linking.parse(url)
|
|
560
|
-
|
|
561
|
-
// But it's easier to test hardcoded strings for the sake of this example
|
|
562
|
-
let path = "/home/item/2";
|
|
563
|
-
|
|
564
|
-
return (
|
|
565
|
-
<Stack.Navigator
|
|
566
|
-
rootScreen={
|
|
567
|
-
<DeepLinks
|
|
568
|
-
path={path}
|
|
569
|
-
handlers={[
|
|
570
|
-
{
|
|
571
|
-
path: "/home/item/:itemId",
|
|
572
|
-
handler: (params: { itemId: string }) => {
|
|
573
|
-
let itemId = params.itemId;
|
|
574
|
-
|
|
575
|
-
// Go to home tab
|
|
576
|
-
navigation.setTabIndex(0);
|
|
577
|
-
|
|
578
|
-
// Push the screen we want
|
|
579
|
-
navigation.pushScreen(
|
|
580
|
-
<Stack.Screen>
|
|
581
|
-
<MyScreen title={`Item: ${itemId}`} />
|
|
582
|
-
</Stack.Screen>
|
|
583
|
-
);
|
|
584
|
-
},
|
|
585
|
-
},
|
|
586
|
-
]}
|
|
587
|
-
>
|
|
588
|
-
<MyTabs />
|
|
589
|
-
</DeepLinks>
|
|
590
|
-
}
|
|
591
|
-
/>
|
|
592
|
-
);
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
function MyTabs() {
|
|
596
|
-
return (
|
|
597
|
-
<Tabs.Navigator
|
|
598
|
-
tabbarPosition="bottom"
|
|
599
|
-
screens={[
|
|
600
|
-
{
|
|
601
|
-
key: "1",
|
|
602
|
-
screen: (
|
|
603
|
-
<Stack.Navigator
|
|
604
|
-
rootScreen={<MyScreen bg="red" title="Home screen" isRoot />}
|
|
605
|
-
/>
|
|
606
|
-
),
|
|
607
|
-
tab: ({ isActive }) => <MyTab text="Home" isActive={isActive} />,
|
|
608
|
-
},
|
|
609
|
-
{
|
|
610
|
-
key: "2",
|
|
611
|
-
screen: (
|
|
612
|
-
<Stack.Navigator
|
|
613
|
-
rootScreen={<MyScreen bg="blue" title="Search screen" isRoot />}
|
|
614
|
-
/>
|
|
615
|
-
),
|
|
616
|
-
tab: ({ isActive }) => <MyTab text="Search" isActive={isActive} />,
|
|
617
|
-
},
|
|
618
|
-
{
|
|
619
|
-
key: "3",
|
|
620
|
-
screen: (
|
|
621
|
-
<Stack.Navigator
|
|
622
|
-
rootScreen={
|
|
623
|
-
<MyScreen bg="purple" title="Settings screen" isRoot />
|
|
624
|
-
}
|
|
625
|
-
/>
|
|
626
|
-
),
|
|
627
|
-
tab: ({ isActive }) => <MyTab text="Settings" isActive={isActive} />,
|
|
628
|
-
},
|
|
629
|
-
]}
|
|
630
|
-
/>
|
|
631
|
-
);
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
function MyTab({ isActive, text }: { isActive?: boolean; text: string }) {
|
|
635
|
-
return (
|
|
636
|
-
<View
|
|
637
|
-
style={{
|
|
638
|
-
padding: 16,
|
|
639
|
-
justifyContent: "center",
|
|
640
|
-
alignItems: "center",
|
|
641
|
-
}}
|
|
642
|
-
>
|
|
643
|
-
<Text style={{ fontSize: 12, fontWeight: isActive ? "bold" : "normal" }}>
|
|
644
|
-
{text}
|
|
645
|
-
</Text>
|
|
646
|
-
</View>
|
|
647
|
-
);
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
function MyScreen({
|
|
651
|
-
bg = "white",
|
|
652
|
-
title = "",
|
|
653
|
-
isRoot = false,
|
|
654
|
-
}: {
|
|
655
|
-
title?: string;
|
|
656
|
-
bg?: string;
|
|
657
|
-
isRoot?: boolean;
|
|
658
|
-
}) {
|
|
659
|
-
return (
|
|
660
|
-
<View style={{ flex: 1, backgroundColor: bg }}>
|
|
661
|
-
<View
|
|
662
|
-
style={{
|
|
663
|
-
flex: 1,
|
|
664
|
-
justifyContent: "center",
|
|
665
|
-
alignItems: "center",
|
|
666
|
-
gap: 4,
|
|
667
|
-
}}
|
|
668
|
-
>
|
|
669
|
-
<Text style={{ fontSize: 26, fontWeight: "semibold" }}>{title}</Text>
|
|
670
|
-
|
|
671
|
-
{!isRoot && (
|
|
672
|
-
<TouchableOpacity
|
|
673
|
-
onPress={() => {
|
|
674
|
-
navigation.popScreen();
|
|
675
|
-
}}
|
|
676
|
-
>
|
|
677
|
-
<Text>Pop</Text>
|
|
678
|
-
</TouchableOpacity>
|
|
679
|
-
)}
|
|
680
|
-
</View>
|
|
681
|
-
</View>
|
|
14
|
+
import {
|
|
15
|
+
createNavigation,
|
|
16
|
+
Navigation,
|
|
17
|
+
Stack,
|
|
18
|
+
Tabs,
|
|
19
|
+
type TabScreenOptions,
|
|
20
|
+
} from "@rn-tools/navigation";
|
|
21
|
+
|
|
22
|
+
const navigation = createNavigation();
|
|
23
|
+
|
|
24
|
+
const tabScreens: TabScreenOptions[] = [
|
|
25
|
+
{
|
|
26
|
+
id: "home",
|
|
27
|
+
screen: <Stack id="home" rootScreen={<HomeScreen />} />,
|
|
28
|
+
tab: ({ isActive, onPress }) => (
|
|
29
|
+
<Pressable onPress={onPress}>
|
|
30
|
+
<Text style={{ fontWeight: isActive ? "bold" : "normal" }}>Home</Text>
|
|
31
|
+
</Pressable>
|
|
32
|
+
),
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: "settings",
|
|
36
|
+
screen: <SettingsScreen />,
|
|
37
|
+
tab: ({ isActive, onPress }) => (
|
|
38
|
+
<Pressable onPress={onPress}>
|
|
39
|
+
<Text style={{ fontWeight: isActive ? "bold" : "normal" }}>Settings</Text>
|
|
40
|
+
</Pressable>
|
|
41
|
+
),
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
export default function App() {
|
|
46
|
+
return (
|
|
47
|
+
<Navigation navigation={navigation}>
|
|
48
|
+
<Tabs id="main-tabs" screens={tabScreens} />
|
|
49
|
+
</Navigation>
|
|
682
50
|
);
|
|
683
51
|
}
|
|
684
52
|
```
|
|
685
53
|
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
If you want to prevent users from popping a screen and potentially losing unsaved data, you can stop the screen from being dismissed by a gesture or pressing the back button.
|
|
689
|
-
|
|
690
|
-
**Note:**: The native header component does not provide a reliable way to prevent going back on iOS, so you'll have to provide your own custom back button by using the `Stack.HeaderLeft` component
|
|
54
|
+
Push and pop screens imperatively:
|
|
691
55
|
|
|
692
56
|
```tsx
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
Text,
|
|
697
|
-
TextInput,
|
|
698
|
-
TouchableOpacity,
|
|
699
|
-
Button,
|
|
700
|
-
View,
|
|
701
|
-
Alert,
|
|
702
|
-
} from "react-native";
|
|
703
|
-
|
|
704
|
-
export function PreventGoingBack() {
|
|
705
|
-
return (
|
|
706
|
-
<Button
|
|
707
|
-
title="Push screen"
|
|
708
|
-
onPress={() => navigation.pushScreen(<MyScreen />)}
|
|
709
|
-
/>
|
|
710
|
-
);
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
function MyScreen() {
|
|
714
|
-
let [input, setInput] = React.useState("");
|
|
715
|
-
|
|
716
|
-
let canGoBack = input.length === 0;
|
|
717
|
-
|
|
718
|
-
let onPressBackButton = React.useCallback(() => {
|
|
719
|
-
if (canGoBack) {
|
|
720
|
-
navigation.popScreen();
|
|
721
|
-
} else {
|
|
722
|
-
Alert.alert("Are you sure you want to go back?", "", [
|
|
723
|
-
{
|
|
724
|
-
text: "Cancel",
|
|
725
|
-
style: "cancel",
|
|
726
|
-
},
|
|
727
|
-
{
|
|
728
|
-
text: "Yes",
|
|
729
|
-
onPress: () => navigation.popScreen(),
|
|
730
|
-
},
|
|
731
|
-
]);
|
|
732
|
-
}
|
|
733
|
-
}, [canGoBack]);
|
|
734
|
-
|
|
735
|
-
return (
|
|
736
|
-
<Stack.Screen
|
|
737
|
-
preventNativeDismiss={!canGoBack}
|
|
738
|
-
nativeBackButtonDismissalEnabled={!canGoBack}
|
|
739
|
-
gestureEnabled={canGoBack}
|
|
740
|
-
header={
|
|
741
|
-
<Stack.Header title="Prevent going back">
|
|
742
|
-
<Stack.HeaderLeft>
|
|
743
|
-
<TouchableOpacity
|
|
744
|
-
onPress={onPressBackButton}
|
|
745
|
-
style={{ opacity: canGoBack ? 1 : 0.4 }}
|
|
746
|
-
>
|
|
747
|
-
<Text>Back</Text>
|
|
748
|
-
</TouchableOpacity>
|
|
749
|
-
</Stack.HeaderLeft>
|
|
750
|
-
</Stack.Header>
|
|
751
|
-
}
|
|
752
|
-
>
|
|
753
|
-
<View style={{ paddingVertical: 48, paddingHorizontal: 16, gap: 16 }}>
|
|
754
|
-
<Text style={{ fontSize: 22, fontWeight: "medium" }}>
|
|
755
|
-
Enter some text and try to go back
|
|
756
|
-
</Text>
|
|
757
|
-
<TextInput
|
|
758
|
-
value={input}
|
|
759
|
-
onChangeText={setInput}
|
|
760
|
-
placeholder="Enter some text"
|
|
761
|
-
onSubmitEditing={() => setInput("")}
|
|
762
|
-
/>
|
|
763
|
-
<Button title="Submit" onPress={() => setInput("")} />
|
|
764
|
-
</View>
|
|
765
|
-
</Stack.Screen>
|
|
766
|
-
);
|
|
767
|
-
}
|
|
57
|
+
navigation.pushScreen(<DetailScreen />, { id: "detail" });
|
|
58
|
+
navigation.popScreen();
|
|
59
|
+
navigation.setActiveTab(1);
|
|
768
60
|
```
|
|
769
61
|
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
Recommended:
|
|
773
|
-
|
|
774
|
-
- Check out the getting started portion of [React Native Testing Library](https://callstack.github.io/react-native-testing-library/docs/start/quick-start)
|
|
62
|
+
When no explicit target is provided, these methods automatically resolve the deepest active stack or tabs instance.
|
|
775
63
|
|
|
776
|
-
|
|
64
|
+
## Docs
|
|
777
65
|
|
|
778
|
-
-
|
|
66
|
+
- [Navigation](docs/navigation.md) — setup, `createNavigation`, `NavigationClient` API, hooks
|
|
67
|
+
- [Stack](docs/stack.md) — stack navigation, pushing/popping, refs, preloading, nesting
|
|
68
|
+
- [Tabs](docs/tabs.md) — tab navigation, tab bar, refs, preloading, nesting with stacks
|