@react-navigation/core 7.21.5 → 7.21.7

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 (91) hide show
  1. package/lib/module/BaseNavigationContainer.js +14 -8
  2. package/lib/module/BaseNavigationContainer.js.map +1 -1
  3. package/lib/module/ConsumedParamsContext.js.map +1 -1
  4. package/lib/module/NavigationIndependentTree.js +8 -4
  5. package/lib/module/NavigationIndependentTree.js.map +1 -1
  6. package/lib/module/SceneView.js +16 -39
  7. package/lib/module/SceneView.js.map +1 -1
  8. package/lib/module/StaticNavigation.js +1 -1
  9. package/lib/module/StaticNavigation.js.map +1 -1
  10. package/lib/module/findFocusedRoute.js +3 -3
  11. package/lib/module/findFocusedRoute.js.map +1 -1
  12. package/lib/module/getActionFromState.js.map +1 -1
  13. package/lib/module/getPathFromState.js +75 -45
  14. package/lib/module/getPathFromState.js.map +1 -1
  15. package/lib/module/getPatternParts.js +15 -2
  16. package/lib/module/getPatternParts.js.map +1 -1
  17. package/lib/module/getStateFromPath.js +47 -21
  18. package/lib/module/getStateFromPath.js.map +1 -1
  19. package/lib/module/getStateFromRouteParams.js +24 -0
  20. package/lib/module/getStateFromRouteParams.js.map +1 -0
  21. package/lib/module/useFocusEvents.js +6 -2
  22. package/lib/module/useFocusEvents.js.map +1 -1
  23. package/lib/module/useLazyValue.js.map +1 -1
  24. package/lib/module/useNavigationBuilder.js +51 -27
  25. package/lib/module/useNavigationBuilder.js.map +1 -1
  26. package/lib/module/useOnAction.js +20 -2
  27. package/lib/module/useOnAction.js.map +1 -1
  28. package/lib/module/useOnPreventRemove.js +1 -3
  29. package/lib/module/useOnPreventRemove.js.map +1 -1
  30. package/lib/module/useRouteCache.js +1 -3
  31. package/lib/module/useRouteCache.js.map +1 -1
  32. package/lib/module/useSyncState.js +3 -2
  33. package/lib/module/useSyncState.js.map +1 -1
  34. package/lib/typescript/src/BaseNavigationContainer.d.ts.map +1 -1
  35. package/lib/typescript/src/ConsumedParamsContext.d.ts +1 -6
  36. package/lib/typescript/src/ConsumedParamsContext.d.ts.map +1 -1
  37. package/lib/typescript/src/EnsureSingleNavigator.d.ts +1 -1
  38. package/lib/typescript/src/EnsureSingleNavigator.d.ts.map +1 -1
  39. package/lib/typescript/src/NavigationIndependentTree.d.ts +1 -1
  40. package/lib/typescript/src/NavigationIndependentTree.d.ts.map +1 -1
  41. package/lib/typescript/src/NavigationProvider.d.ts +1 -1
  42. package/lib/typescript/src/NavigationProvider.d.ts.map +1 -1
  43. package/lib/typescript/src/PreventRemoveProvider.d.ts +1 -1
  44. package/lib/typescript/src/PreventRemoveProvider.d.ts.map +1 -1
  45. package/lib/typescript/src/SceneView.d.ts +2 -1
  46. package/lib/typescript/src/SceneView.d.ts.map +1 -1
  47. package/lib/typescript/src/StaticContainer.d.ts +1 -1
  48. package/lib/typescript/src/StaticContainer.d.ts.map +1 -1
  49. package/lib/typescript/src/findFocusedRoute.d.ts.map +1 -1
  50. package/lib/typescript/src/getPathFromState.d.ts.map +1 -1
  51. package/lib/typescript/src/getPatternParts.d.ts.map +1 -1
  52. package/lib/typescript/src/getStateFromPath.d.ts.map +1 -1
  53. package/lib/typescript/src/getStateFromRouteParams.d.ts +3 -0
  54. package/lib/typescript/src/getStateFromRouteParams.d.ts.map +1 -0
  55. package/lib/typescript/src/theming/ThemeProvider.d.ts +1 -1
  56. package/lib/typescript/src/theming/ThemeProvider.d.ts.map +1 -1
  57. package/lib/typescript/src/types.d.ts +15 -9
  58. package/lib/typescript/src/types.d.ts.map +1 -1
  59. package/lib/typescript/src/useComponent.d.ts +1 -1
  60. package/lib/typescript/src/useComponent.d.ts.map +1 -1
  61. package/lib/typescript/src/useFocusEvents.d.ts.map +1 -1
  62. package/lib/typescript/src/useLazyValue.d.ts.map +1 -1
  63. package/lib/typescript/src/useNavigationBuilder.d.ts +1 -1
  64. package/lib/typescript/src/useNavigationBuilder.d.ts.map +1 -1
  65. package/lib/typescript/src/useNavigationState.d.ts +1 -1
  66. package/lib/typescript/src/useNavigationState.d.ts.map +1 -1
  67. package/lib/typescript/src/useOnAction.d.ts.map +1 -1
  68. package/lib/typescript/src/useOnPreventRemove.d.ts.map +1 -1
  69. package/lib/typescript/src/useRouteCache.d.ts.map +1 -1
  70. package/lib/typescript/src/useSyncState.d.ts.map +1 -1
  71. package/package.json +7 -7
  72. package/src/BaseNavigationContainer.tsx +16 -10
  73. package/src/ConsumedParamsContext.tsx +1 -6
  74. package/src/NavigationIndependentTree.tsx +8 -5
  75. package/src/SceneView.tsx +18 -51
  76. package/src/StaticNavigation.tsx +2 -2
  77. package/src/findFocusedRoute.tsx +5 -3
  78. package/src/getActionFromState.tsx +3 -1
  79. package/src/getPathFromState.tsx +130 -59
  80. package/src/getPatternParts.tsx +15 -2
  81. package/src/getStateFromPath.tsx +76 -17
  82. package/src/getStateFromRouteParams.tsx +60 -0
  83. package/src/types.tsx +42 -24
  84. package/src/useDescriptors.tsx +1 -1
  85. package/src/useFocusEvents.tsx +7 -5
  86. package/src/useLazyValue.tsx +2 -2
  87. package/src/useNavigationBuilder.tsx +82 -37
  88. package/src/useOnAction.tsx +25 -0
  89. package/src/useOnPreventRemove.tsx +4 -3
  90. package/src/useRouteCache.tsx +1 -4
  91. package/src/useSyncState.tsx +3 -2
package/src/types.tsx CHANGED
@@ -190,7 +190,7 @@ export type DefaultNavigatorOptions<
190
190
  ScreenOptions,
191
191
  EventMap
192
192
  >,
193
- RouteProp<ParamList>
193
+ ScreenRouteProp<ParamList>
194
194
  >
195
195
  >;
196
196
  children: React.ReactNode;
@@ -202,7 +202,7 @@ export type DefaultNavigatorOptions<
202
202
  screenListeners?:
203
203
  | ScreenListeners<State, EventMap>
204
204
  | ((props: {
205
- route: RouteProp<ParamList>;
205
+ route: ScreenRouteProp<ParamList>;
206
206
  navigation: Navigation;
207
207
  }) => ScreenListeners<State, EventMap>);
208
208
 
@@ -212,7 +212,7 @@ export type DefaultNavigatorOptions<
212
212
  screenOptions?:
213
213
  | ScreenOptions
214
214
  | ((props: {
215
- route: RouteProp<ParamList>;
215
+ route: ScreenRouteProp<ParamList>;
216
216
  navigation: Navigation;
217
217
  theme: ReactNavigation.Theme;
218
218
  }) => ScreenOptions);
@@ -647,6 +647,13 @@ export type RouteProp<
647
647
  RouteName extends keyof ParamList = Keyof<ParamList>,
648
648
  > = Route<Extract<RouteName, string>, ParamList[RouteName]>;
649
649
 
650
+ type ScreenRouteProp<
651
+ ParamList extends ParamListBase,
652
+ RouteName extends keyof ParamList = Keyof<ParamList>,
653
+ > = {
654
+ [Name in Extract<RouteName, string>]: RouteProp<ParamList, Name>;
655
+ }[Extract<RouteName, string>];
656
+
650
657
  export type CompositeNavigationProp<
651
658
  A extends NavigationProp<ParamListBase, string, any, any, any>,
652
659
  B extends NavigationHelpersCommon<ParamListBase, any>,
@@ -709,7 +716,7 @@ export type ScreenLayoutArgs<
709
716
  ScreenOptions extends {},
710
717
  Navigation,
711
718
  > = {
712
- route: RouteProp<ParamList, RouteName>;
719
+ route: ScreenRouteProp<ParamList, RouteName>;
713
720
  options: ScreenOptions;
714
721
  navigation: Navigation;
715
722
  theme: ReactNavigation.Theme;
@@ -896,7 +903,7 @@ export type RouteGroupConfig<
896
903
  screenOptions?:
897
904
  | ScreenOptions
898
905
  | ((props: {
899
- route: RouteProp<ParamList, keyof ParamList>;
906
+ route: ScreenRouteProp<ParamList>;
900
907
  navigation: Navigation;
901
908
  theme: ReactNavigation.Theme;
902
909
  }) => ScreenOptions);
@@ -1103,25 +1110,36 @@ export type NavigatorTypeBag<
1103
1110
  Navigator: Navigator;
1104
1111
  };
1105
1112
 
1106
- type TypedNavigatorStaticComponent<Bag extends NavigatorTypeBagBase> =
1107
- React.ComponentType<
1108
- Partial<
1113
+ type StaticParamListForConfig<Config> =
1114
+ Config extends StaticNavigationConfig['config']
1115
+ ? StaticParamList<{ config: Config }> extends infer ParamList extends
1116
+ ParamListBase
1117
+ ? ParamList
1118
+ : ParamListBase
1119
+ : ParamListBase;
1120
+
1121
+ type TypedNavigatorStaticComponent<
1122
+ Bag extends NavigatorTypeBagBase,
1123
+ Config,
1124
+ > = React.ComponentType<
1125
+ Partial<
1126
+ Omit<
1109
1127
  Omit<
1110
- React.ComponentProps<
1111
- TypedNavigatorInternal<
1112
- Bag['ParamList'],
1113
- Bag['NavigatorID'],
1114
- Bag['State'],
1115
- Bag['ScreenOptions'],
1116
- Bag['EventMap'],
1117
- Bag['NavigationList'],
1118
- Bag['Navigator']
1119
- >['Navigator']
1128
+ React.ComponentProps<Bag['Navigator']>,
1129
+ keyof DefaultNavigatorOptions<any, any, any, any, any, any>
1130
+ > &
1131
+ DefaultNavigatorOptions<
1132
+ StaticParamListForConfig<Config>,
1133
+ Bag['NavigatorID'],
1134
+ Bag['State'],
1135
+ Bag['ScreenOptions'],
1136
+ Bag['EventMap'],
1137
+ Bag['NavigationList'][keyof Bag['ParamList']]
1120
1138
  >,
1121
- 'children'
1122
- >
1139
+ 'children'
1123
1140
  >
1124
- >;
1141
+ >
1142
+ >;
1125
1143
 
1126
1144
  type TypedNavigatorStaticDecorated<
1127
1145
  Bag extends NavigatorTypeBagBase,
@@ -1154,10 +1172,10 @@ type TypedNavigatorStatic<
1154
1172
  config: Config;
1155
1173
  with: (
1156
1174
  Component: React.ComponentType<{
1157
- Navigator: TypedNavigatorStaticComponent<Bag>;
1175
+ Navigator: TypedNavigatorStaticComponent<Bag, Config>;
1158
1176
  }>
1159
1177
  ) => TypedNavigatorStaticDecorated<Bag, Config>;
1160
- getComponent: () => TypedNavigatorStaticComponent<Bag>;
1178
+ getComponent: () => TypedNavigatorStaticComponent<Bag, Config>;
1161
1179
  } & PrivateValueStore<[Bag['ParamList'], Bag['NavigationList'], unknown]>;
1162
1180
 
1163
1181
  export type TypedNavigator<
@@ -1284,7 +1302,7 @@ type PathConfigAlias = {
1284
1302
  export type PathConfig<ParamList extends {}> = Partial<PathConfigAlias> & {
1285
1303
  /**
1286
1304
  * An object mapping the param name to a function which converts the param value to a string.
1287
- * By default, all params are converted to strings using `String(value)`.
1305
+ * By default, arrays and null are preserved for query params, and other values are converted using `String(value)`.
1288
1306
  *
1289
1307
  * @example
1290
1308
  * ```js
@@ -311,7 +311,7 @@ export function useDescriptors<
311
311
  render() {
312
312
  return element;
313
313
  },
314
- options: customOptions as ScreenOptions,
314
+ options: customOptions,
315
315
  };
316
316
 
317
317
  return acc;
@@ -45,7 +45,12 @@ export function useFocusEvents<State extends NavigationState>({
45
45
  React.useEffect(() => {
46
46
  const lastFocusedKey = lastFocusedKeyRef.current;
47
47
 
48
- lastFocusedKeyRef.current = currentFocusedKey;
48
+ const isFocused = navigation ? navigation.isFocused() : true;
49
+
50
+ if (isFocused) {
51
+ // Remember the current route as focused if the navigator is focused
52
+ lastFocusedKeyRef.current = currentFocusedKey;
53
+ }
49
54
 
50
55
  // We wouldn't have `lastFocusedKey` on initial mount
51
56
  // Fire focus event for the current route on mount if there's no parent navigator
@@ -55,10 +60,7 @@ export function useFocusEvents<State extends NavigationState>({
55
60
 
56
61
  // We should only emit events when the focused key changed and navigator is focused
57
62
  // When navigator is not focused, screens inside shouldn't receive focused status either
58
- if (
59
- lastFocusedKey === currentFocusedKey ||
60
- !(navigation ? navigation.isFocused() : true)
61
- ) {
63
+ if (lastFocusedKey === currentFocusedKey || !isFocused) {
62
64
  return;
63
65
  }
64
66
 
@@ -1,11 +1,11 @@
1
1
  import * as React from 'react';
2
2
 
3
- export function useLazyValue<T>(create: () => T) {
3
+ export function useLazyValue<T>(create: () => T): T {
4
4
  const lazyRef = React.useRef<T>(undefined);
5
5
 
6
6
  if (lazyRef.current === undefined) {
7
7
  lazyRef.current = create();
8
8
  }
9
9
 
10
- return lazyRef.current as T;
10
+ return lazyRef.current;
11
11
  }
@@ -19,7 +19,6 @@ import { ConsumedParamsContext } from './ConsumedParamsContext';
19
19
  import { deepFreeze } from './deepFreeze';
20
20
  import { Group } from './Group';
21
21
  import { isArrayEqual } from './isArrayEqual';
22
- import { isRecordEqual } from './isRecordEqual';
23
22
  import { NavigationBuilderContext } from './NavigationBuilderContext';
24
23
  import { NavigationHelpersContext } from './NavigationHelpersContext';
25
24
  import { NavigationMetaContext } from './NavigationMetaContext';
@@ -105,7 +104,7 @@ const getRouteConfigsFromChildren = <
105
104
  EventMap extends EventMapBase,
106
105
  >(
107
106
  children: React.ReactNode,
108
- groupKey?: string,
107
+ groupKeys: string[] = [],
109
108
  groupOptions?: ScreenConfigWithParent<
110
109
  State,
111
110
  ScreenOptions,
@@ -148,7 +147,7 @@ const getRouteConfigsFromChildren = <
148
147
  }
149
148
 
150
149
  acc.push({
151
- keys: [groupKey, child.props.navigationKey],
150
+ keys: [...groupKeys, child.props.navigationKey],
152
151
  options: groupOptions,
153
152
  layout: groupLayout,
154
153
  props: child.props as RouteConfig<
@@ -178,7 +177,9 @@ const getRouteConfigsFromChildren = <
178
177
  acc.push(
179
178
  ...getRouteConfigsFromChildren<State, ScreenOptions, EventMap>(
180
179
  child.props.children as React.ReactNode,
181
- child.props.navigationKey,
180
+ child.props.navigationKey != null
181
+ ? [...groupKeys, child.props.navigationKey]
182
+ : groupKeys,
182
183
  // FIXME
183
184
  // @ts-expect-error: add validation
184
185
  child.type !== Group
@@ -286,10 +287,23 @@ const getRouteConfigsFromChildren = <
286
287
  return configs;
287
288
  };
288
289
 
289
- const getStateFromParams = (params: NavigatorRoute['params']) => {
290
+ const getStateFromParams = (
291
+ params: NavigatorRoute['params'],
292
+ type: string
293
+ ): NavigationState | PartialState<NavigationState> | undefined => {
290
294
  const state = params?.state;
291
295
 
292
296
  if (isNavigationState(state)) {
297
+ if (state.type != null && state.type !== type) {
298
+ if (process.env.NODE_ENV !== 'production') {
299
+ console.error(
300
+ `The state passed in the params is for a navigator of type '${state.type}', but it was passed to a navigator of type '${type}'. This can happen if the state was meant for a different navigator. The state will be discarded.`
301
+ );
302
+ }
303
+
304
+ return undefined;
305
+ }
306
+
293
307
  return state;
294
308
  } else if (typeof params?.screen === 'string' && params?.initial !== false) {
295
309
  return {
@@ -341,7 +355,7 @@ export function useNavigationBuilder<
341
355
 
342
356
  const isNestedParamsConsumed =
343
357
  typeof route?.params === 'object' && route.params != null
344
- ? consumedParams?.isConsumed(route.params)
358
+ ? consumedParams?.has(route.params)
345
359
  : false;
346
360
 
347
361
  const {
@@ -399,7 +413,7 @@ export function useNavigationBuilder<
399
413
  ScreenConfigWithParent<State, ScreenOptions, EventMap>
400
414
  > = {};
401
415
 
402
- const routeKeyList: Record<string, React.Key | undefined> = {};
416
+ const routeKeyList: Record<string, (string | undefined)[]> = {};
403
417
  const routeParamList: Record<string, object | undefined> = {};
404
418
  const routeGetIdList: RouterConfigOptions['routeGetIdList'] = {};
405
419
 
@@ -413,7 +427,7 @@ export function useNavigationBuilder<
413
427
  }
414
428
 
415
429
  screens[name] = config;
416
- routeKeyList[name] = config.keys.map((key) => key ?? '').join(':');
430
+ routeKeyList[name] = config.keys;
417
431
  routeParamList[name] = config.props.initialParams;
418
432
 
419
433
  Object.assign(routeGetIdList, { [name]: config.props.getId });
@@ -547,7 +561,7 @@ export function useNavigationBuilder<
547
561
  } else {
548
562
  const paramsForState = isNestedParamsConsumed ? undefined : route?.params;
549
563
  const stateFromParams = paramsForState
550
- ? getStateFromParams(paramsForState)
564
+ ? getStateFromParams(paramsForState, router.type)
551
565
  : undefined;
552
566
 
553
567
  const stateBeforeInitialization = (stateFromParams ?? currentState) as
@@ -569,13 +583,21 @@ export function useNavigationBuilder<
569
583
 
570
584
  if (
571
585
  stateBeforeInitialization != null &&
586
+ isStateValid(stateBeforeInitialization) &&
572
587
  options.UNSTABLE_routeNamesChangeBehavior === 'lastUnhandled' &&
573
588
  doesStateHaveOnlyInvalidRoutes(stateBeforeInitialization)
574
589
  ) {
575
590
  return [stateBeforeInitialization, hydratedState, true, paramsForState];
576
591
  }
577
592
 
578
- return [undefined, hydratedState, false, paramsForState];
593
+ return [
594
+ undefined,
595
+ hydratedState,
596
+ false,
597
+ // Current state is already initialized
598
+ // So params weren't used, and need to be handled later
599
+ isStateInitialized(currentState) ? undefined : paramsForState,
600
+ ];
579
601
  }
580
602
  // We explicitly don't include routeNames, route.params etc. in the dep list
581
603
  // below. We want to avoid forcing a new state to be calculated in those cases
@@ -593,6 +615,16 @@ export function useNavigationBuilder<
593
615
 
594
616
  const previousRouteKeyList = previousRouteKeyListRef.current;
595
617
 
618
+ // Names of the screens whose navigation key changed since the last render
619
+ const routeKeyChanges = Object.keys(routeKeyList).filter((name) => {
620
+ const current = routeKeyList[name];
621
+ const previous = previousRouteKeyList[name];
622
+
623
+ return (
624
+ current != null && previous != null && !isArrayEqual(current, previous)
625
+ );
626
+ });
627
+
596
628
  const [unhandledState, setUnhandledState] = React.useState<
597
629
  NavigationState | PartialState<NavigationState> | undefined
598
630
  >(stateBeforeInitialization);
@@ -620,10 +652,14 @@ export function useNavigationBuilder<
620
652
  let nextState: State = state;
621
653
  let shouldClearUnhandledState = false;
622
654
 
623
- // Previously unhandled state is now valid again
624
- // And current state no longer has any valid routes
625
- // We should reuse the unhandled state instead of re-calculating the state
626
- if (
655
+ if (unhandledState != null && !isStateValid(unhandledState)) {
656
+ // The stored unhandled state is for a different type of navigator
657
+ // It can't be used with this navigator, so we discard it
658
+ setUnhandledState(undefined);
659
+ } else if (
660
+ // Previously unhandled state is now valid again
661
+ // And current state no longer has any valid routes
662
+ // We should reuse the unhandled state instead of re-calculating the state
627
663
  unhandledState?.routes.every((r) => routeNames.includes(r.name)) &&
628
664
  state?.routes.every((r) => !routeNames.includes(r.name))
629
665
  ) {
@@ -638,18 +674,14 @@ export function useNavigationBuilder<
638
674
  );
639
675
  } else if (
640
676
  !isArrayEqual(state.routeNames, routeNames) ||
641
- !isRecordEqual(routeKeyList, previousRouteKeyList)
677
+ routeKeyChanges.length !== 0
642
678
  ) {
643
679
  // When the list of route names change, the router should handle it to remove invalid routes
644
680
  nextState = router.getStateForRouteNamesChange(state, {
645
681
  routeNames,
646
682
  routeParamList,
647
683
  routeGetIdList,
648
- routeKeyChanges: Object.keys(routeKeyList).filter(
649
- (name) =>
650
- name in previousRouteKeyList &&
651
- routeKeyList[name] !== previousRouteKeyList[name]
652
- ),
684
+ routeKeyChanges,
653
685
  });
654
686
  }
655
687
 
@@ -657,21 +689,24 @@ export function useNavigationBuilder<
657
689
 
658
690
  if (route?.params && !didConsumeNestedParams) {
659
691
  let action: CommonActions.Action | undefined;
660
- const stateFromParams = route.params.state;
661
692
 
662
- if (isNavigationState(stateFromParams) && !isNestedParamsConsumed) {
693
+ if (isNavigationState(route.params.state) && !isNestedParamsConsumed) {
663
694
  didConsumeNestedParams = true;
664
695
 
665
- if (
666
- options.UNSTABLE_routeNamesChangeBehavior === 'lastUnhandled' &&
667
- doesStateHaveOnlyInvalidRoutes(stateFromParams)
668
- ) {
669
- if (stateFromParams !== unhandledState) {
670
- setUnhandledState(stateFromParams);
696
+ const stateFromParams = getStateFromParams(route.params, router.type);
697
+
698
+ if (stateFromParams != null) {
699
+ if (
700
+ options.UNSTABLE_routeNamesChangeBehavior === 'lastUnhandled' &&
701
+ doesStateHaveOnlyInvalidRoutes(stateFromParams)
702
+ ) {
703
+ if (stateFromParams !== unhandledState) {
704
+ setUnhandledState(stateFromParams);
705
+ }
706
+ } else {
707
+ // If the route was updated with new state, we should reset to it
708
+ action = CommonActions.reset(stateFromParams);
671
709
  }
672
- } else {
673
- // If the route was updated with new state, we should reset to it
674
- action = CommonActions.reset(stateFromParams);
675
710
  }
676
711
  } else if (
677
712
  typeof route.params.screen === 'string' &&
@@ -684,7 +719,7 @@ export function useNavigationBuilder<
684
719
  options.UNSTABLE_routeNamesChangeBehavior === 'lastUnhandled' &&
685
720
  !routeNames.includes(route.params.screen)
686
721
  ) {
687
- const state = getStateFromParams(route.params);
722
+ const state = getStateFromParams(route.params, router.type);
688
723
 
689
724
  if (state != null && !deepEqual(state, unhandledState)) {
690
725
  setUnhandledState(state);
@@ -713,6 +748,18 @@ export function useNavigationBuilder<
713
748
  })
714
749
  : null;
715
750
 
751
+ if (
752
+ process.env.NODE_ENV !== 'production' &&
753
+ action != null &&
754
+ updatedState === null
755
+ ) {
756
+ const names = routeNames.map((name) => `'${name}'`).join(', ');
757
+
758
+ console.error(
759
+ `The 'state' or 'screen' passed in the params couldn't be applied to the navigator with the screens: ${names}. This can happen if the params contain names of screens that don't exist in the navigator. The navigation specified in the params will be ignored.`
760
+ );
761
+ }
762
+
716
763
  nextState =
717
764
  updatedState !== null
718
765
  ? router.getRehydratedState(updatedState, {
@@ -723,18 +770,16 @@ export function useNavigationBuilder<
723
770
  : nextState;
724
771
  }
725
772
 
726
- const setConsumedParams = consumedParams?.setConsumed;
727
-
728
773
  React.useEffect(() => {
729
774
  if (
730
- setConsumedParams &&
775
+ consumedParams &&
731
776
  didConsumeNestedParams &&
732
777
  typeof route?.params === 'object' &&
733
778
  route.params != null
734
779
  ) {
735
- setConsumedParams(route.params);
780
+ consumedParams.set(route.params, true);
736
781
  }
737
- }, [didConsumeNestedParams, route?.params, setConsumedParams]);
782
+ }, [consumedParams, didConsumeNestedParams, route?.params]);
738
783
 
739
784
  const shouldUpdate = state !== nextState;
740
785
 
@@ -52,6 +52,7 @@ export function useOnAction<State extends NavigationState>({
52
52
  onRouteFocus: onRouteFocusParent,
53
53
  addListener: addListenerParent,
54
54
  onDispatchAction,
55
+ flushUpdates,
55
56
  } = React.useContext(NavigationBuilderContext);
56
57
  const navigationInChildEnabled = React.useContext(
57
58
  DeprecatedNavigationInChildContext
@@ -69,6 +70,11 @@ export function useOnAction<State extends NavigationState>({
69
70
  action: NavigationAction,
70
71
  visitedNavigators: Set<string> = new Set<string>()
71
72
  ) => {
73
+ // Apply any pending state updates scheduled during render before handling the action
74
+ // Otherwise the action will be handled with an outdated state,
75
+ // and the pending updates will overwrite the changes from the action
76
+ flushUpdates();
77
+
72
78
  const state = getState();
73
79
 
74
80
  // Since actions can bubble both up and down, they could come to the same navigator again
@@ -91,6 +97,17 @@ export function useOnAction<State extends NavigationState>({
91
97
  result =
92
98
  result === null && action.target === state.key ? state : result;
93
99
 
100
+ if (result !== null && result.stale !== false) {
101
+ // Some actions (e.g. `RESET`) may return a stale state from the router
102
+ // We rehydrate it before storing so the stored state is always usable
103
+ // Otherwise anything reading the state before the next render
104
+ // (e.g. another dispatch immediately after) would get an outdated state
105
+ result = router.getRehydratedState(
106
+ result,
107
+ routerConfigOptionsRef.current
108
+ );
109
+ }
110
+
94
111
  if (result !== null) {
95
112
  if (state !== result) {
96
113
  const isPrevented = shouldPreventRemove(
@@ -107,6 +124,13 @@ export function useOnAction<State extends NavigationState>({
107
124
  return true;
108
125
  }
109
126
 
127
+ if (getState() !== state) {
128
+ // Listeners for 'beforeRemove' may dispatch actions that change the state
129
+ // Then the result we have is outdated and would overwrite those changes
130
+ // So we handle the action again to calculate the result with the latest state
131
+ return onAction(action, new Set<string>());
132
+ }
133
+
110
134
  onDispatchAction(action, false);
111
135
 
112
136
  setState(result);
@@ -158,6 +182,7 @@ export function useOnAction<State extends NavigationState>({
158
182
  actionListeners,
159
183
  beforeRemoveListeners,
160
184
  emitter,
185
+ flushUpdates,
161
186
  getState,
162
187
  navigationInChildEnabled,
163
188
  key,
@@ -34,9 +34,10 @@ export const shouldPreventRemove = (
34
34
  .filter((route) => !nextRouteKeys.includes(route.key))
35
35
  .reverse();
36
36
 
37
- const visitedRouteKeys: Set<string> =
38
- // @ts-expect-error: add this property to mark that we've already emitted this action
39
- action[VISITED_ROUTE_KEYS] ?? new Set<string>();
37
+ const visitedRouteKeys =
38
+ VISITED_ROUTE_KEYS in action && action[VISITED_ROUTE_KEYS] instanceof Set
39
+ ? action[VISITED_ROUTE_KEYS]
40
+ : new Set<string>();
40
41
 
41
42
  const beforeRemoveAction = {
42
43
  ...action,
@@ -41,10 +41,7 @@ export function useRouteCache<State extends NavigationState>(
41
41
  // FIXME: since the state is updated with mutation, the route object cannot be frozen
42
42
  // As a workaround, loop through the object and make the properties readonly
43
43
  // Only needed once per proxy - skip if we're reusing a previously-frozen one
44
- for (const key in proxy) {
45
- // @ts-expect-error: this is fine since we are looping through the object
46
- const value = proxy[key];
47
-
44
+ for (const [key, value] of Object.entries(proxy)) {
48
45
  Object.defineProperty(proxy, key, {
49
46
  enumerable: true,
50
47
  configurable: true,
@@ -25,9 +25,10 @@ const createStore = <T,>(getInitialState: () => T) => {
25
25
 
26
26
  const setState = (newState: T) => {
27
27
  state = deepFreeze(newState);
28
- didUpdate = true;
29
28
 
30
- if (!isBatching) {
29
+ if (isBatching) {
30
+ didUpdate = true;
31
+ } else {
31
32
  listeners.forEach((listener) => listener());
32
33
  }
33
34
  };