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