@react-navigation/native-stack 6.7.0 → 6.8.0

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.
Files changed (28) hide show
  1. package/lib/commonjs/utils/useDismissedRouteError.js +29 -0
  2. package/lib/commonjs/utils/useDismissedRouteError.js.map +1 -0
  3. package/lib/commonjs/utils/useInvalidPreventRemoveError.js +33 -0
  4. package/lib/commonjs/utils/useInvalidPreventRemoveError.js.map +1 -0
  5. package/lib/commonjs/views/HeaderConfig.js +1 -1
  6. package/lib/commonjs/views/HeaderConfig.js.map +1 -1
  7. package/lib/commonjs/views/NativeStackView.js +9 -5
  8. package/lib/commonjs/views/NativeStackView.js.map +1 -1
  9. package/lib/commonjs/views/NativeStackView.native.js +50 -17
  10. package/lib/commonjs/views/NativeStackView.native.js.map +1 -1
  11. package/lib/module/utils/useDismissedRouteError.js +17 -0
  12. package/lib/module/utils/useDismissedRouteError.js.map +1 -0
  13. package/lib/module/utils/useInvalidPreventRemoveError.js +20 -0
  14. package/lib/module/utils/useInvalidPreventRemoveError.js.map +1 -0
  15. package/lib/module/views/HeaderConfig.js +1 -1
  16. package/lib/module/views/HeaderConfig.js.map +1 -1
  17. package/lib/module/views/NativeStackView.js +10 -6
  18. package/lib/module/views/NativeStackView.js.map +1 -1
  19. package/lib/module/views/NativeStackView.native.js +50 -19
  20. package/lib/module/views/NativeStackView.native.js.map +1 -1
  21. package/lib/typescript/src/utils/useDismissedRouteError.d.ts +5 -0
  22. package/lib/typescript/src/utils/useInvalidPreventRemoveError.d.ts +2 -0
  23. package/package.json +7 -5
  24. package/src/utils/useDismissedRouteError.tsx +30 -0
  25. package/src/utils/useInvalidPreventRemoveError.tsx +31 -0
  26. package/src/views/HeaderConfig.tsx +1 -1
  27. package/src/views/NativeStackView.native.tsx +59 -27
  28. package/src/views/NativeStackView.tsx +20 -12
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  getDefaultHeaderHeight,
3
3
  getHeaderTitle,
4
+ HeaderBackContext,
4
5
  HeaderHeightContext,
5
6
  HeaderShownContext,
6
7
  SafeAreaProviderCompat,
@@ -12,6 +13,7 @@ import {
12
13
  Route,
13
14
  StackActions,
14
15
  StackNavigationState,
16
+ usePreventRemoveContext,
15
17
  useTheme,
16
18
  } from '@react-navigation/native';
17
19
  import * as React from 'react';
@@ -34,6 +36,8 @@ import type {
34
36
  NativeStackNavigationHelpers,
35
37
  NativeStackNavigationOptions,
36
38
  } from '../types';
39
+ import useDismissedRouteError from '../utils/useDismissedRouteError';
40
+ import useInvalidPreventRemoveError from '../utils/useInvalidPreventRemoveError';
37
41
  import DebugContainer from './DebugContainer';
38
42
  import HeaderConfig from './HeaderConfig';
39
43
 
@@ -116,6 +120,8 @@ type SceneViewProps = {
116
120
  onAppear: () => void;
117
121
  onDisappear: () => void;
118
122
  onDismissed: ScreenProps['onDismissed'];
123
+ onHeaderBackButtonClicked: () => void;
124
+ onNativeDismissCancelled: ScreenProps['onDismissed'];
119
125
  };
120
126
 
121
127
  const SceneView = ({
@@ -127,6 +133,8 @@ const SceneView = ({
127
133
  onAppear,
128
134
  onDisappear,
129
135
  onDismissed,
136
+ onHeaderBackButtonClicked,
137
+ onNativeDismissCancelled,
130
138
  }: SceneViewProps) => {
131
139
  const { route, navigation, options, render } = descriptor;
132
140
  const {
@@ -134,6 +142,7 @@ const SceneView = ({
134
142
  animationTypeForReplace = 'push',
135
143
  gestureEnabled,
136
144
  header,
145
+ headerBackButtonMenuEnabled,
137
146
  headerShown,
138
147
  autoHideHomeIndicator,
139
148
  navigationBarColor,
@@ -202,6 +211,9 @@ const SceneView = ({
202
211
 
203
212
  const isParentHeaderShown = React.useContext(HeaderShownContext);
204
213
  const parentHeaderHeight = React.useContext(HeaderHeightContext);
214
+ const parentHeaderBack = React.useContext(HeaderBackContext);
215
+
216
+ const { preventedRoutes } = usePreventRemoveContext();
205
217
 
206
218
  const defaultHeaderHeight = getDefaultHeaderHeight(frame, isModal, topInset);
207
219
 
@@ -209,6 +221,16 @@ const SceneView = ({
209
221
  React.useState(defaultHeaderHeight);
210
222
 
211
223
  const headerHeight = header ? customHeaderHeight : defaultHeaderHeight;
224
+ const headerBack = previousDescriptor
225
+ ? {
226
+ title: getHeaderTitle(
227
+ previousDescriptor.options,
228
+ previousDescriptor.route.name
229
+ ),
230
+ }
231
+ : parentHeaderBack;
232
+
233
+ const isRemovePrevented = preventedRoutes[route.key]?.preventRemove;
212
234
 
213
235
  return (
214
236
  <Screen
@@ -243,6 +265,12 @@ const SceneView = ({
243
265
  onDisappear={onDisappear}
244
266
  onDismissed={onDismissed}
245
267
  isNativeStack
268
+ // Props for enabling preventing removal in native-stack
269
+ nativeBackButtonDismissalEnabled={false} // on Android
270
+ // @ts-expect-error prop not publicly exported from rn-screens
271
+ preventNativeDismiss={isRemovePrevented} // on iOS
272
+ onHeaderBackButtonClicked={onHeaderBackButtonClicked}
273
+ onNativeDismissCancelled={onNativeDismissCancelled}
246
274
  >
247
275
  <NavigationContext.Provider value={navigation}>
248
276
  <NavigationRouteContext.Provider value={route}>
@@ -263,14 +291,7 @@ const SceneView = ({
263
291
  }}
264
292
  >
265
293
  {header({
266
- back: previousDescriptor
267
- ? {
268
- title: getHeaderTitle(
269
- previousDescriptor.options,
270
- previousDescriptor.route.name
271
- ),
272
- }
273
- : undefined,
294
+ back: headerBack,
274
295
  options,
275
296
  route,
276
297
  navigation,
@@ -280,9 +301,19 @@ const SceneView = ({
280
301
  <HeaderConfig
281
302
  {...options}
282
303
  route={route}
304
+ headerBackButtonMenuEnabled={
305
+ isRemovePrevented !== undefined
306
+ ? !isRemovePrevented
307
+ : headerBackButtonMenuEnabled
308
+ }
283
309
  headerShown={isHeaderInPush}
284
310
  headerHeight={headerHeight}
285
- canGoBack={index !== 0}
311
+ headerBackTitle={
312
+ options.headerBackTitle !== undefined
313
+ ? options.headerBackTitle
314
+ : headerBack?.title
315
+ }
316
+ canGoBack={headerBack !== undefined}
286
317
  />
287
318
  )}
288
319
  <MaybeNestedStack
@@ -291,7 +322,9 @@ const SceneView = ({
291
322
  presentation={presentation}
292
323
  headerHeight={headerHeight}
293
324
  >
294
- {render()}
325
+ <HeaderBackContext.Provider value={headerBack}>
326
+ {render()}
327
+ </HeaderBackContext.Provider>
295
328
  </MaybeNestedStack>
296
329
  </HeaderHeightContext.Provider>
297
330
  </HeaderShownContext.Provider>
@@ -308,24 +341,9 @@ type Props = {
308
341
  };
309
342
 
310
343
  function NativeStackViewInner({ state, navigation, descriptors }: Props) {
311
- const [nextDismissedKey, setNextDismissedKey] = React.useState<string | null>(
312
- null
313
- );
314
-
315
- const dismissedRouteName = nextDismissedKey
316
- ? state.routes.find((route) => route.key === nextDismissedKey)?.name
317
- : null;
344
+ const { setNextDismissedKey } = useDismissedRouteError(state);
318
345
 
319
- React.useEffect(() => {
320
- if (dismissedRouteName) {
321
- const message =
322
- `The screen '${dismissedRouteName}' was removed natively but didn't get removed from JS state. ` +
323
- `This can happen if the action was prevented in a 'beforeRemove' listener, which is not fully supported in native-stack.\n\n` +
324
- `Consider using 'gestureEnabled: false' to prevent back gesture and use a custom back button with 'headerLeft' option to override the native behavior.`;
325
-
326
- console.error(message);
327
- }
328
- }, [dismissedRouteName]);
346
+ useInvalidPreventRemoveError(descriptors);
329
347
 
330
348
  return (
331
349
  <ScreenStack style={styles.container}>
@@ -375,6 +393,20 @@ function NativeStackViewInner({ state, navigation, descriptors }: Props) {
375
393
 
376
394
  setNextDismissedKey(route.key);
377
395
  }}
396
+ onHeaderBackButtonClicked={() => {
397
+ navigation.dispatch({
398
+ ...StackActions.pop(),
399
+ source: route.key,
400
+ target: state.key,
401
+ });
402
+ }}
403
+ onNativeDismissCancelled={(event) => {
404
+ navigation.dispatch({
405
+ ...StackActions.pop(event.nativeEvent.dismissCount),
406
+ source: route.key,
407
+ target: state.key,
408
+ });
409
+ }}
378
410
  />
379
411
  );
380
412
  })}
@@ -2,6 +2,7 @@ import {
2
2
  getHeaderTitle,
3
3
  Header,
4
4
  HeaderBackButton,
5
+ HeaderBackContext,
5
6
  SafeAreaProviderCompat,
6
7
  Screen,
7
8
  } from '@react-navigation/elements';
@@ -31,12 +32,13 @@ const TRANSPARENT_PRESENTATIONS = [
31
32
  ];
32
33
 
33
34
  export default function NativeStackView({ state, descriptors }: Props) {
35
+ const parentHeaderBack = React.useContext(HeaderBackContext);
36
+
34
37
  return (
35
38
  <SafeAreaProviderCompat>
36
39
  <View style={styles.container}>
37
40
  {state.routes.map((route, i) => {
38
41
  const isFocused = state.index === i;
39
- const canGoBack = i !== 0;
40
42
  const previousKey = state.routes[i - 1]?.key;
41
43
  const nextKey = state.routes[i + 1]?.key;
42
44
  const previousDescriptor = previousKey
@@ -45,6 +47,17 @@ export default function NativeStackView({ state, descriptors }: Props) {
45
47
  const nextDescriptor = nextKey ? descriptors[nextKey] : undefined;
46
48
  const { options, navigation, render } = descriptors[route.key];
47
49
 
50
+ const headerBack = previousDescriptor
51
+ ? {
52
+ title: getHeaderTitle(
53
+ previousDescriptor.options,
54
+ previousDescriptor.route.name
55
+ ),
56
+ }
57
+ : parentHeaderBack;
58
+
59
+ const canGoBack = headerBack !== undefined;
60
+
48
61
  const {
49
62
  header,
50
63
  headerShown,
@@ -77,14 +90,7 @@ export default function NativeStackView({ state, descriptors }: Props) {
77
90
  header={
78
91
  header !== undefined ? (
79
92
  header({
80
- back: previousDescriptor
81
- ? {
82
- title: getHeaderTitle(
83
- previousDescriptor.options,
84
- previousDescriptor.route.name
85
- ),
86
- }
87
- : undefined,
93
+ back: headerBack,
88
94
  options,
89
95
  route,
90
96
  navigation,
@@ -161,9 +167,11 @@ export default function NativeStackView({ state, descriptors }: Props) {
161
167
  : null,
162
168
  ]}
163
169
  >
164
- <View style={[styles.contentContainer, contentStyle]}>
165
- {render()}
166
- </View>
170
+ <HeaderBackContext.Provider value={headerBack}>
171
+ <View style={[styles.contentContainer, contentStyle]}>
172
+ {render()}
173
+ </View>
174
+ </HeaderBackContext.Provider>
167
175
  </Screen>
168
176
  );
169
177
  })}