@react-navigation/core 7.21.5 → 7.21.6

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 (87) 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 +8 -6
  25. package/lib/module/useNavigationBuilder.js.map +1 -1
  26. package/lib/module/useOnPreventRemove.js +1 -3
  27. package/lib/module/useOnPreventRemove.js.map +1 -1
  28. package/lib/module/useRouteCache.js +1 -3
  29. package/lib/module/useRouteCache.js.map +1 -1
  30. package/lib/module/useSyncState.js +3 -2
  31. package/lib/module/useSyncState.js.map +1 -1
  32. package/lib/typescript/src/BaseNavigationContainer.d.ts.map +1 -1
  33. package/lib/typescript/src/ConsumedParamsContext.d.ts +1 -6
  34. package/lib/typescript/src/ConsumedParamsContext.d.ts.map +1 -1
  35. package/lib/typescript/src/EnsureSingleNavigator.d.ts +1 -1
  36. package/lib/typescript/src/EnsureSingleNavigator.d.ts.map +1 -1
  37. package/lib/typescript/src/NavigationIndependentTree.d.ts +1 -1
  38. package/lib/typescript/src/NavigationIndependentTree.d.ts.map +1 -1
  39. package/lib/typescript/src/NavigationProvider.d.ts +1 -1
  40. package/lib/typescript/src/NavigationProvider.d.ts.map +1 -1
  41. package/lib/typescript/src/PreventRemoveProvider.d.ts +1 -1
  42. package/lib/typescript/src/PreventRemoveProvider.d.ts.map +1 -1
  43. package/lib/typescript/src/SceneView.d.ts +2 -1
  44. package/lib/typescript/src/SceneView.d.ts.map +1 -1
  45. package/lib/typescript/src/StaticContainer.d.ts +1 -1
  46. package/lib/typescript/src/StaticContainer.d.ts.map +1 -1
  47. package/lib/typescript/src/findFocusedRoute.d.ts.map +1 -1
  48. package/lib/typescript/src/getPathFromState.d.ts.map +1 -1
  49. package/lib/typescript/src/getPatternParts.d.ts.map +1 -1
  50. package/lib/typescript/src/getStateFromPath.d.ts.map +1 -1
  51. package/lib/typescript/src/getStateFromRouteParams.d.ts +3 -0
  52. package/lib/typescript/src/getStateFromRouteParams.d.ts.map +1 -0
  53. package/lib/typescript/src/theming/ThemeProvider.d.ts +1 -1
  54. package/lib/typescript/src/theming/ThemeProvider.d.ts.map +1 -1
  55. package/lib/typescript/src/types.d.ts +15 -9
  56. package/lib/typescript/src/types.d.ts.map +1 -1
  57. package/lib/typescript/src/useComponent.d.ts +1 -1
  58. package/lib/typescript/src/useComponent.d.ts.map +1 -1
  59. package/lib/typescript/src/useFocusEvents.d.ts.map +1 -1
  60. package/lib/typescript/src/useLazyValue.d.ts.map +1 -1
  61. package/lib/typescript/src/useNavigationBuilder.d.ts +1 -1
  62. package/lib/typescript/src/useNavigationBuilder.d.ts.map +1 -1
  63. package/lib/typescript/src/useNavigationState.d.ts +1 -1
  64. package/lib/typescript/src/useNavigationState.d.ts.map +1 -1
  65. package/lib/typescript/src/useOnPreventRemove.d.ts.map +1 -1
  66. package/lib/typescript/src/useRouteCache.d.ts.map +1 -1
  67. package/lib/typescript/src/useSyncState.d.ts.map +1 -1
  68. package/package.json +7 -7
  69. package/src/BaseNavigationContainer.tsx +16 -10
  70. package/src/ConsumedParamsContext.tsx +1 -6
  71. package/src/NavigationIndependentTree.tsx +8 -5
  72. package/src/SceneView.tsx +18 -51
  73. package/src/StaticNavigation.tsx +2 -2
  74. package/src/findFocusedRoute.tsx +5 -3
  75. package/src/getActionFromState.tsx +3 -1
  76. package/src/getPathFromState.tsx +130 -59
  77. package/src/getPatternParts.tsx +15 -2
  78. package/src/getStateFromPath.tsx +76 -17
  79. package/src/getStateFromRouteParams.tsx +60 -0
  80. package/src/types.tsx +42 -24
  81. package/src/useDescriptors.tsx +1 -1
  82. package/src/useFocusEvents.tsx +7 -5
  83. package/src/useLazyValue.tsx +2 -2
  84. package/src/useNavigationBuilder.tsx +12 -7
  85. package/src/useOnPreventRemove.tsx +4 -3
  86. package/src/useRouteCache.tsx +1 -4
  87. package/src/useSyncState.tsx +3 -2
@@ -26,7 +26,12 @@ type RouteConfig = {
26
26
  screen: string;
27
27
  regex?: RegExp;
28
28
  segments: string[];
29
- params: { screen: string; name: string; index: number }[];
29
+ params: {
30
+ screen: string;
31
+ name: string;
32
+ index: number;
33
+ regex?: RegExp;
34
+ }[];
30
35
  routeNames: string[];
31
36
  parse?: ParseConfig;
32
37
  explicitParamNames?: Set<string>;
@@ -53,6 +58,7 @@ type ConfigResources = {
53
58
  initialRoutes: InitialRouteConfig[];
54
59
  configs: RouteConfig[];
55
60
  configsByScreen: Record<string, RouteConfig[]>;
61
+ prefixRegex?: RegExp;
56
62
  };
57
63
 
58
64
  const NESTED_SCREEN_PARAM_NAMES = [
@@ -64,6 +70,17 @@ const NESTED_SCREEN_PARAM_NAMES = [
64
70
  'pop',
65
71
  ];
66
72
 
73
+ const getStaticSegmentPattern = (segment: string) =>
74
+ Array.from(segment, (char) => {
75
+ const encoded = encodeURIComponent(char);
76
+ const percentEncoded =
77
+ encoded === char
78
+ ? `%${char.charCodeAt(0).toString(16).padStart(2, '0').toUpperCase()}`
79
+ : encoded;
80
+
81
+ return `(?:${escape(char)}|${escape(percentEncoded)})`;
82
+ }).join('');
83
+
67
84
  const getExplicitParamNames = (parse?: ParseConfig) => {
68
85
  const names = Object.entries(parse ?? {}).map(([name]) => name);
69
86
 
@@ -95,7 +112,7 @@ export function getStateFromPath<ParamList extends {}>(
95
112
  path: string,
96
113
  options?: Options<ParamList>
97
114
  ): ResultState | undefined {
98
- const { initialRoutes, configs, configsByScreen } =
115
+ const { initialRoutes, configs, configsByScreen, prefixRegex } =
99
116
  getConfigResources(options);
100
117
 
101
118
  const screens = options?.screens;
@@ -103,24 +120,20 @@ export function getStateFromPath<ParamList extends {}>(
103
120
  let remaining = path
104
121
  .replace(/\/+/g, '/') // Replace multiple slash (//) with single ones
105
122
  .replace(/^\//, '') // Remove extra leading slash
106
- .replace(/\?.*$/, ''); // Remove query params which we will handle later
123
+ .replace(/\?.*$/, '') // Remove query params which we will handle later
124
+ .replace(/%[0-9a-f]{2}/gi, (match) => match.toUpperCase());
107
125
 
108
126
  // Make sure there is a trailing slash
109
127
  remaining = remaining.endsWith('/') ? remaining : `${remaining}/`;
110
128
 
111
- const prefix = options?.path?.replace(/^\//, ''); // Remove extra leading slash
112
-
113
- if (prefix) {
114
- // Make sure there is a trailing slash
115
- const normalizedPrefix = prefix.endsWith('/') ? prefix : `${prefix}/`;
129
+ if (prefixRegex) {
130
+ const prefixMatch = remaining.match(prefixRegex);
116
131
 
117
- // If the path doesn't start with the prefix, it's not a match
118
- if (!remaining.startsWith(normalizedPrefix)) {
132
+ if (prefixMatch == null) {
119
133
  return undefined;
120
134
  }
121
135
 
122
- // Remove the prefix from the path
123
- remaining = remaining.replace(normalizedPrefix, '');
136
+ remaining = remaining.slice(prefixMatch[0].length);
124
137
  }
125
138
 
126
139
  if (screens === undefined) {
@@ -219,6 +232,19 @@ function prepareConfigResources(options?: Options<{}>) {
219
232
 
220
233
  const initialRoutes = getInitialRoutes(options);
221
234
  const configs = getSortedNormalizedConfigs(initialRoutes, options?.screens);
235
+ const prefix = options?.path?.replace(/^\//, '');
236
+
237
+ let prefixRegex: RegExp | undefined;
238
+
239
+ if (prefix) {
240
+ const normalizedPrefix = prefix.endsWith('/') ? prefix : `${prefix}/`;
241
+ const prefixPattern = normalizedPrefix
242
+ .split('/')
243
+ .map(getStaticSegmentPattern)
244
+ .join('/');
245
+
246
+ prefixRegex = new RegExp(`^${prefixPattern}`);
247
+ }
222
248
 
223
249
  const configsByScreen: Record<string, RouteConfig[]> = {};
224
250
  const configsByPattern = new Map<string, RouteConfig>();
@@ -237,6 +263,7 @@ function prepareConfigResources(options?: Options<{}>) {
237
263
  initialRoutes,
238
264
  configs,
239
265
  configsByScreen,
266
+ prefixRegex,
240
267
  };
241
268
  }
242
269
 
@@ -264,6 +291,7 @@ function getSortedNormalizedConfigs(
264
291
  createNormalizedConfigs(key, screens, initialRoutes, [], [], [])
265
292
  )
266
293
  )
294
+ .map((config, order) => ({ ...config, order }))
267
295
  .sort((a, b) => {
268
296
  // Sort config from most specific to least specific:
269
297
  // - more segments
@@ -275,7 +303,21 @@ function getSortedNormalizedConfigs(
275
303
  // If 2 patterns are same, move the one with less route names up
276
304
  // This is an error state, so it's only useful for consistent error messages
277
305
  if (isArrayEqual(a.segments, b.segments)) {
278
- return b.routeNames.join('>').localeCompare(a.routeNames.join('>'));
306
+ if (
307
+ a.routeNames.length > b.routeNames.length &&
308
+ arrayStartsWith(a.routeNames, b.routeNames)
309
+ ) {
310
+ return -1;
311
+ }
312
+
313
+ if (
314
+ b.routeNames.length > a.routeNames.length &&
315
+ arrayStartsWith(b.routeNames, a.routeNames)
316
+ ) {
317
+ return 1;
318
+ }
319
+
320
+ return a.routeNames.length - b.routeNames.length || a.order - b.order;
279
321
  }
280
322
 
281
323
  // If one of the patterns starts with the other, it's more exhaustive
@@ -441,6 +483,15 @@ const matchAgainstConfigs = (
441
483
  break;
442
484
  }
443
485
 
486
+ if (
487
+ param.regex &&
488
+ value !== decoded &&
489
+ !param.regex.test(decoded)
490
+ ) {
491
+ hasInvalidParam = true;
492
+ break;
493
+ }
494
+
444
495
  const parser = routeConfig.parse?.[param.name];
445
496
 
446
497
  paramEntries.push([param.name, parser ? parser(decoded) : decoded]);
@@ -642,12 +693,18 @@ const createConfigItem = (
642
693
  `^(${parts
643
694
  .map((it, i) => {
644
695
  if (it.param) {
645
- const reg = it.regex || '[^/]+';
696
+ const reg = it.regex
697
+ ? `(?:${it.regex})|(?=[^/]*%[0-9A-F]{2})[^/]+`
698
+ : '[^/]+';
646
699
 
647
700
  return `(((?<param_${i}>${reg})\\/)${it.optional ? '?' : ''})`;
648
701
  }
649
702
 
650
- return `${it.segment === '*' ? '.*' : escape(it.segment)}\\/`;
703
+ if (it.segment === '*') {
704
+ return `.*\\/`;
705
+ }
706
+
707
+ return `${getStaticSegmentPattern(it.segment)}\\/`;
651
708
  })
652
709
  .join('')})$`
653
710
  )
@@ -666,6 +723,7 @@ const createConfigItem = (
666
723
  index,
667
724
  screen: part.screen,
668
725
  name: part.param,
726
+ regex: part.regex ? new RegExp(`^(?:${part.regex})$`) : undefined,
669
727
  });
670
728
 
671
729
  if (part.screen === screen) {
@@ -813,8 +871,9 @@ const parseQueryParams = (
813
871
  hasNestedScreens = false,
814
872
  routeParams?: Record<string, unknown>
815
873
  ) => {
816
- const query = path.split('?')[1];
817
- const params: Record<string, unknown> = queryString.parse(query);
874
+ const queryIndex = path.indexOf('?');
875
+ const query = queryIndex === -1 ? undefined : path.slice(queryIndex + 1);
876
+ const params: Record<string, unknown> = query ? queryString.parse(query) : {};
818
877
 
819
878
  for (const name of pathParamNames) {
820
879
  // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
@@ -0,0 +1,60 @@
1
+ import type { NavigationState, PartialState } from '@react-navigation/routers';
2
+
3
+ export function getStateFromRouteParams(
4
+ params: object | undefined
5
+ ): PartialState<NavigationState> | NavigationState | undefined {
6
+ if (params == null || typeof params !== 'object') {
7
+ return undefined;
8
+ }
9
+
10
+ if (
11
+ 'state' in params &&
12
+ params.state &&
13
+ typeof params.state === 'object' &&
14
+ 'routes' in params.state &&
15
+ Array.isArray(params.state.routes) &&
16
+ params.state.routes.every(
17
+ (route) =>
18
+ typeof route === 'object' &&
19
+ route != null &&
20
+ 'name' in route &&
21
+ typeof route.name === 'string'
22
+ )
23
+ ) {
24
+ // @ts-expect-error this is fine 🔥
25
+ return params.state;
26
+ }
27
+
28
+ if (
29
+ 'screen' in params &&
30
+ params.screen &&
31
+ typeof params.screen === 'string'
32
+ ) {
33
+ return {
34
+ routes: [
35
+ {
36
+ name: params.screen,
37
+ params:
38
+ 'params' in params &&
39
+ typeof params.params === 'object' &&
40
+ params.params != null
41
+ ? params.params
42
+ : undefined,
43
+ path:
44
+ 'path' in params && typeof params.path === 'string'
45
+ ? params.path
46
+ : undefined,
47
+ // @ts-expect-error this is fine 🔥
48
+ state:
49
+ 'params' in params &&
50
+ typeof params.params === 'object' &&
51
+ params.params != null
52
+ ? getStateFromRouteParams(params.params)
53
+ : undefined,
54
+ },
55
+ ],
56
+ };
57
+ }
58
+
59
+ return undefined;
60
+ }
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
  }
@@ -341,7 +341,7 @@ export function useNavigationBuilder<
341
341
 
342
342
  const isNestedParamsConsumed =
343
343
  typeof route?.params === 'object' && route.params != null
344
- ? consumedParams?.isConsumed(route.params)
344
+ ? consumedParams?.has(route.params)
345
345
  : false;
346
346
 
347
347
  const {
@@ -575,7 +575,14 @@ export function useNavigationBuilder<
575
575
  return [stateBeforeInitialization, hydratedState, true, paramsForState];
576
576
  }
577
577
 
578
- return [undefined, hydratedState, false, paramsForState];
578
+ return [
579
+ undefined,
580
+ hydratedState,
581
+ false,
582
+ // Current state is already initialized
583
+ // So params weren't used, and need to be handled later
584
+ isStateInitialized(currentState) ? undefined : paramsForState,
585
+ ];
579
586
  }
580
587
  // We explicitly don't include routeNames, route.params etc. in the dep list
581
588
  // below. We want to avoid forcing a new state to be calculated in those cases
@@ -723,18 +730,16 @@ export function useNavigationBuilder<
723
730
  : nextState;
724
731
  }
725
732
 
726
- const setConsumedParams = consumedParams?.setConsumed;
727
-
728
733
  React.useEffect(() => {
729
734
  if (
730
- setConsumedParams &&
735
+ consumedParams &&
731
736
  didConsumeNestedParams &&
732
737
  typeof route?.params === 'object' &&
733
738
  route.params != null
734
739
  ) {
735
- setConsumedParams(route.params);
740
+ consumedParams.set(route.params, true);
736
741
  }
737
- }, [didConsumeNestedParams, route?.params, setConsumedParams]);
742
+ }, [consumedParams, didConsumeNestedParams, route?.params]);
738
743
 
739
744
  const shouldUpdate = state !== nextState;
740
745
 
@@ -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
  };