@react-navigation/core 8.0.0-alpha.24 → 8.0.0-alpha.25

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 (136) hide show
  1. package/lib/module/BaseNavigationContainer.js +5 -2
  2. package/lib/module/BaseNavigationContainer.js.map +1 -1
  3. package/lib/module/DataLoading.js +102 -0
  4. package/lib/module/DataLoading.js.map +1 -0
  5. package/lib/module/NavigationIndependentTree.js +7 -6
  6. package/lib/module/NavigationIndependentTree.js.map +1 -1
  7. package/lib/module/NavigationProvider.js +12 -12
  8. package/lib/module/NavigationProvider.js.map +1 -1
  9. package/lib/module/PreventRemoveContext.js.map +1 -1
  10. package/lib/module/PreventRemoveProvider.js +34 -24
  11. package/lib/module/PreventRemoveProvider.js.map +1 -1
  12. package/lib/module/StaticNavigation.js +14 -6
  13. package/lib/module/StaticNavigation.js.map +1 -1
  14. package/lib/module/StaticTreeContext.js +5 -0
  15. package/lib/module/StaticTreeContext.js.map +1 -0
  16. package/lib/module/checkSerializable.js +7 -9
  17. package/lib/module/checkSerializable.js.map +1 -1
  18. package/lib/module/createNavigationContainerRef.js +6 -8
  19. package/lib/module/createNavigationContainerRef.js.map +1 -1
  20. package/lib/module/createNavigatorFactory.js.map +1 -1
  21. package/lib/module/getActionFromState.js +11 -11
  22. package/lib/module/getActionFromState.js.map +1 -1
  23. package/lib/module/getPathFromState.js +38 -39
  24. package/lib/module/getPathFromState.js.map +1 -1
  25. package/lib/module/getPatternParts.js +2 -2
  26. package/lib/module/getPatternParts.js.map +1 -1
  27. package/lib/module/getStateFromPath.js +72 -45
  28. package/lib/module/getStateFromPath.js.map +1 -1
  29. package/lib/module/index.js +1 -0
  30. package/lib/module/index.js.map +1 -1
  31. package/lib/module/types.js.map +1 -1
  32. package/lib/module/useEventEmitter.js +1 -1
  33. package/lib/module/useEventEmitter.js.map +1 -1
  34. package/lib/module/useKeyedChildListeners.js +4 -2
  35. package/lib/module/useKeyedChildListeners.js.map +1 -1
  36. package/lib/module/useNavigation.js +2 -15
  37. package/lib/module/useNavigation.js.map +1 -1
  38. package/lib/module/useNavigationBuilder.js +4 -14
  39. package/lib/module/useNavigationBuilder.js.map +1 -1
  40. package/lib/module/useNavigationCache.js +19 -14
  41. package/lib/module/useNavigationCache.js.map +1 -1
  42. package/lib/module/useNavigationHelpers.js +27 -9
  43. package/lib/module/useNavigationHelpers.js.map +1 -1
  44. package/lib/module/useNavigationState.js +69 -36
  45. package/lib/module/useNavigationState.js.map +1 -1
  46. package/lib/module/useOnAction.js +21 -2
  47. package/lib/module/useOnAction.js.map +1 -1
  48. package/lib/module/useOptionsGetters.js +3 -1
  49. package/lib/module/useOptionsGetters.js.map +1 -1
  50. package/lib/module/usePreventRemove.js +9 -2
  51. package/lib/module/usePreventRemove.js.map +1 -1
  52. package/lib/module/useRouteCache.js +14 -7
  53. package/lib/module/useRouteCache.js.map +1 -1
  54. package/lib/module/useScheduleUpdate.js +8 -8
  55. package/lib/module/useScheduleUpdate.js.map +1 -1
  56. package/lib/module/useSyncState.js +4 -1
  57. package/lib/module/useSyncState.js.map +1 -1
  58. package/lib/module/validatePathConfig.js.map +1 -1
  59. package/lib/typescript/src/BaseNavigationContainer.d.ts.map +1 -1
  60. package/lib/typescript/src/DataLoading.d.ts +20 -0
  61. package/lib/typescript/src/DataLoading.d.ts.map +1 -0
  62. package/lib/typescript/src/NavigationProvider.d.ts +0 -1
  63. package/lib/typescript/src/NavigationProvider.d.ts.map +1 -1
  64. package/lib/typescript/src/PreventRemoveContext.d.ts +1 -0
  65. package/lib/typescript/src/PreventRemoveContext.d.ts.map +1 -1
  66. package/lib/typescript/src/PreventRemoveProvider.d.ts.map +1 -1
  67. package/lib/typescript/src/StaticNavigation.d.ts +65 -39
  68. package/lib/typescript/src/StaticNavigation.d.ts.map +1 -1
  69. package/lib/typescript/src/StaticTreeContext.d.ts +4 -0
  70. package/lib/typescript/src/StaticTreeContext.d.ts.map +1 -0
  71. package/lib/typescript/src/checkSerializable.d.ts +4 -2
  72. package/lib/typescript/src/checkSerializable.d.ts.map +1 -1
  73. package/lib/typescript/src/createNavigationContainerRef.d.ts.map +1 -1
  74. package/lib/typescript/src/createNavigatorFactory.d.ts +9 -5
  75. package/lib/typescript/src/createNavigatorFactory.d.ts.map +1 -1
  76. package/lib/typescript/src/getActionFromState.d.ts.map +1 -1
  77. package/lib/typescript/src/getPathFromState.d.ts.map +1 -1
  78. package/lib/typescript/src/getStateFromPath.d.ts.map +1 -1
  79. package/lib/typescript/src/index.d.ts +2 -1
  80. package/lib/typescript/src/index.d.ts.map +1 -1
  81. package/lib/typescript/src/types.d.ts +19 -13
  82. package/lib/typescript/src/types.d.ts.map +1 -1
  83. package/lib/typescript/src/useKeyedChildListeners.d.ts.map +1 -1
  84. package/lib/typescript/src/useNavigation.d.ts.map +1 -1
  85. package/lib/typescript/src/useNavigationBuilder.d.ts +1 -1
  86. package/lib/typescript/src/useNavigationBuilder.d.ts.map +1 -1
  87. package/lib/typescript/src/useNavigationCache.d.ts.map +1 -1
  88. package/lib/typescript/src/useNavigationHelpers.d.ts +3 -4
  89. package/lib/typescript/src/useNavigationHelpers.d.ts.map +1 -1
  90. package/lib/typescript/src/useNavigationState.d.ts +11 -1
  91. package/lib/typescript/src/useNavigationState.d.ts.map +1 -1
  92. package/lib/typescript/src/useOnAction.d.ts.map +1 -1
  93. package/lib/typescript/src/useOptionsGetters.d.ts.map +1 -1
  94. package/lib/typescript/src/usePreventRemove.d.ts.map +1 -1
  95. package/lib/typescript/src/usePreventRemoveContext.d.ts +1 -0
  96. package/lib/typescript/src/usePreventRemoveContext.d.ts.map +1 -1
  97. package/lib/typescript/src/useRouteCache.d.ts.map +1 -1
  98. package/lib/typescript/src/useScheduleUpdate.d.ts +5 -4
  99. package/lib/typescript/src/useScheduleUpdate.d.ts.map +1 -1
  100. package/lib/typescript/src/useSyncState.d.ts.map +1 -1
  101. package/lib/typescript/src/utilities.d.ts +17 -0
  102. package/lib/typescript/src/utilities.d.ts.map +1 -1
  103. package/lib/typescript/src/validatePathConfig.d.ts.map +1 -1
  104. package/package.json +3 -5
  105. package/src/BaseNavigationContainer.tsx +9 -2
  106. package/src/DataLoading.tsx +139 -0
  107. package/src/NavigationIndependentTree.tsx +11 -11
  108. package/src/NavigationProvider.tsx +13 -16
  109. package/src/PreventRemoveContext.tsx +1 -0
  110. package/src/PreventRemoveProvider.tsx +48 -43
  111. package/src/StaticNavigation.tsx +157 -93
  112. package/src/StaticTreeContext.tsx +7 -0
  113. package/src/checkSerializable.tsx +20 -14
  114. package/src/createNavigationContainerRef.tsx +12 -9
  115. package/src/createNavigatorFactory.tsx +16 -28
  116. package/src/getActionFromState.tsx +18 -16
  117. package/src/getPathFromState.tsx +69 -65
  118. package/src/getPatternParts.tsx +2 -2
  119. package/src/getStateFromPath.tsx +107 -66
  120. package/src/index.tsx +2 -0
  121. package/src/types.tsx +62 -50
  122. package/src/useEventEmitter.tsx +1 -1
  123. package/src/useKeyedChildListeners.tsx +4 -2
  124. package/src/useNavigation.tsx +2 -21
  125. package/src/useNavigationBuilder.tsx +6 -16
  126. package/src/useNavigationCache.tsx +26 -19
  127. package/src/useNavigationHelpers.tsx +26 -9
  128. package/src/useNavigationState.tsx +90 -60
  129. package/src/useOnAction.tsx +49 -1
  130. package/src/useOptionsGetters.tsx +4 -1
  131. package/src/usePreventRemove.tsx +11 -2
  132. package/src/useRouteCache.tsx +19 -11
  133. package/src/useScheduleUpdate.tsx +8 -7
  134. package/src/useSyncState.tsx +5 -1
  135. package/src/utilities.tsx +28 -0
  136. package/src/validatePathConfig.tsx +1 -0
@@ -43,6 +43,7 @@ type RouteConfig = {
43
43
  routeNames: string[];
44
44
  parse?: ParseConfig | undefined;
45
45
  explicitParamNames?: Set<string> | undefined;
46
+ pathParamNames: Set<string>;
46
47
  hasNestedScreens: boolean;
47
48
  shared: boolean;
48
49
  };
@@ -202,23 +203,21 @@ export function getStateFromPath<ParamList extends {}>(
202
203
  remaining = remaining.replace(normalizedPrefix, '');
203
204
  }
204
205
 
205
- const decodedSegments: string[] = [];
206
+ if (screens === undefined) {
207
+ // When no config is specified, use the path segments as route names
208
+ const routes: ParsedRoute[] = [];
206
209
 
207
- for (const segment of remaining.split('/')) {
208
- if (!segment) {
209
- continue;
210
- }
210
+ for (const segment of remaining.split('/')) {
211
+ if (!segment) {
212
+ continue;
213
+ }
211
214
 
212
- try {
213
- decodedSegments.push(decodeURIComponent(segment));
214
- } catch {
215
- return undefined;
215
+ try {
216
+ routes.push({ name: decodeURIComponent(segment) });
217
+ } catch {
218
+ return undefined;
219
+ }
216
220
  }
217
- }
218
-
219
- if (screens === undefined) {
220
- // When no config is specified, use the path segments as route names
221
- const routes = decodedSegments.map((name) => ({ name }));
222
221
 
223
222
  if (routes.length) {
224
223
  return createNestedStateObject(path, routes, initialRoutes);
@@ -248,7 +247,29 @@ export function getStateFromPath<ParamList extends {}>(
248
247
 
249
248
  // We match the whole path against the regex instead of segments
250
249
  // This makes sure matches such as wildcard will catch any unmatched routes, even if nested
250
+ const firstRawSegment = remaining.split('/')[0];
251
+
252
+ let firstDecodedSegment: string | undefined;
253
+
254
+ try {
255
+ firstDecodedSegment =
256
+ firstRawSegment != null ? decodeURIComponent(firstRawSegment) : undefined;
257
+ } catch {
258
+ firstDecodedSegment = undefined;
259
+ }
260
+
251
261
  for (const config of configs) {
262
+ // Regex matching is expensive, so skip configs that can't match this path.
263
+ if (
264
+ !canMatchFirstSegment(
265
+ config.segments[0],
266
+ firstDecodedSegment,
267
+ firstRawSegment
268
+ )
269
+ ) {
270
+ continue;
271
+ }
272
+
252
273
  const routes = matchAgainstConfig(remaining, config, configsByScreen);
253
274
 
254
275
  if (routes === undefined) {
@@ -287,6 +308,26 @@ export function getStateFromPath<ParamList extends {}>(
287
308
  return undefined;
288
309
  }
289
310
 
311
+ const canMatchFirstSegment = (
312
+ configSegment: string | undefined,
313
+ decodedSegment: string | undefined,
314
+ rawSegment: string | undefined
315
+ ) => {
316
+ if (decodedSegment === undefined) {
317
+ return true;
318
+ }
319
+
320
+ if (configSegment === undefined) {
321
+ return false;
322
+ }
323
+
324
+ if (configSegment === '*' || configSegment.startsWith(':')) {
325
+ return true;
326
+ }
327
+
328
+ return configSegment === decodedSegment || configSegment === rawSegment;
329
+ };
330
+
290
331
  /**
291
332
  * Reference to the last used config resources. This is used to avoid recomputing the config resources when the options are the same.
292
333
  */
@@ -295,11 +336,17 @@ const cachedConfigResources = new WeakMap<Options<{}>, ConfigResources>();
295
336
  function getConfigResources<ParamList extends {}>(
296
337
  options: Options<ParamList> | undefined
297
338
  ) {
298
- if (!options) return prepareConfigResources();
339
+ if (!options) {
340
+ return prepareConfigResources();
341
+ }
299
342
 
300
343
  const cached = cachedConfigResources.get(options);
301
344
 
302
- if (cached) return cached;
345
+ if (cached) {
346
+ return cached;
347
+ }
348
+
349
+ validatePathConfig(options);
303
350
 
304
351
  const resources = prepareConfigResources(options);
305
352
 
@@ -309,21 +356,23 @@ function getConfigResources<ParamList extends {}>(
309
356
  }
310
357
 
311
358
  function prepareConfigResources(options?: Options<{}>) {
312
- if (options) {
313
- validatePathConfig(options);
314
- }
315
-
316
359
  const initialRoutes = getInitialRoutes(options);
317
360
  const configs = getSortedNormalizedConfigs(initialRoutes, options?.screens);
318
361
 
319
- checkForDuplicatedConfigs(configs);
320
-
321
362
  const configsByScreen: Record<string, RouteConfig[]> = {};
322
363
  const configsByPattern: Record<string, RouteConfig[]> = {};
323
364
 
324
365
  for (const c of configs) {
325
366
  (configsByScreen[c.screen] ??= []).push(c);
326
- (configsByPattern[c.pattern] ??= []).push(c);
367
+
368
+ const existing = configsByPattern[c.pattern];
369
+
370
+ if (existing) {
371
+ checkForDuplicatedConfigs(existing[existing.length - 1], c);
372
+ existing.push(c);
373
+ } else {
374
+ configsByPattern[c.pattern] = [c];
375
+ }
327
376
  }
328
377
 
329
378
  return {
@@ -453,37 +502,31 @@ function getSortedNormalizedConfigs(
453
502
  });
454
503
  }
455
504
 
456
- function checkForDuplicatedConfigs(configs: RouteConfig[]) {
457
- // Check for duplicate patterns in the config
458
- const patterns: Record<string, RouteConfig> = {};
459
-
460
- for (const config of configs) {
461
- const pattern = config.pattern;
462
- const existing = patterns[pattern];
463
-
464
- if (existing) {
465
- const a = existing.routeNames;
466
- const b = config.routeNames;
467
-
468
- // It's not a problem if the path string omitted from a inner most screen
469
- // For example, it's ok if a path resolves to `A > B > C` or `A > B`
470
- const intersects =
471
- a.length > b.length
472
- ? b.every((it, i) => a[i] === it)
473
- : a.every((it, i) => b[i] === it);
474
-
475
- if (!intersects && (!existing.shared || !config.shared)) {
476
- throw new Error(
477
- `Found conflicting screens with the same pattern. The pattern '${
478
- pattern
479
- }' resolves to both '${a.join(' > ')}' and '${b.join(
480
- ' > '
481
- )}'. Patterns must be unique and cannot resolve to more than one screen unless shared: true is specified.`
482
- );
483
- }
484
- }
505
+ // Throw if two configs resolve to the same pattern but conflicting screens
506
+ function checkForDuplicatedConfigs(
507
+ existing: RouteConfig | undefined,
508
+ config: RouteConfig
509
+ ) {
510
+ if (!existing) {
511
+ return;
512
+ }
485
513
 
486
- patterns[pattern] = config;
514
+ const a = existing.routeNames;
515
+ const b = config.routeNames;
516
+
517
+ // It's not a problem if the path string omitted from a inner most screen
518
+ // For example, it's ok if a path resolves to `A > B > C` or `A > B`
519
+ const intersects =
520
+ a.length > b.length ? arrayStartsWith(a, b) : arrayStartsWith(b, a);
521
+
522
+ if (!intersects && (!existing.shared || !config.shared)) {
523
+ throw new Error(
524
+ `Found conflicting screens with the same pattern. The pattern '${
525
+ config.pattern
526
+ }' resolves to both '${a.join(' > ')}' and '${b.join(
527
+ ' > '
528
+ )}'. Patterns must be unique and cannot resolve to more than one screen unless shared: true is specified.`
529
+ );
487
530
  }
488
531
  }
489
532
 
@@ -521,15 +564,13 @@ const matchAgainstConfig = (
521
564
  if (routeConfig && match.groups) {
522
565
  params = {};
523
566
 
524
- for (const key in match.groups) {
525
- const value = match.groups[key];
526
- const index = Number(key.replace(PARAM_GROUP_PREFIX, ''));
527
- const param = routeConfig.params.find((it) => it.index === index);
528
-
529
- if (!param || param.screen !== routeName) {
567
+ for (const param of routeConfig.params) {
568
+ if (param.screen !== routeName) {
530
569
  continue;
531
570
  }
532
571
 
572
+ const value = match.groups[`${PARAM_GROUP_PREFIX}${param.index}`];
573
+
533
574
  if (value == null) {
534
575
  params[param.name] = undefined;
535
576
  hasParams = true;
@@ -748,6 +789,7 @@ const createConfigItem = (
748
789
 
749
790
  const segments: string[] = [];
750
791
  const params: RouteConfig['params'] = [];
792
+ const pathParamNames = new Set<string>();
751
793
 
752
794
  if (parts.length) {
753
795
  let regexString = '^(';
@@ -765,6 +807,10 @@ const createConfigItem = (
765
807
  screen: part.screen,
766
808
  name: part.param,
767
809
  });
810
+
811
+ if (part.screen === screen) {
812
+ pathParamNames.add(part.param);
813
+ }
768
814
  } else {
769
815
  regexString += `${part.segment === '*' ? '.*' : escape(part.segment)}\\/`;
770
816
  }
@@ -788,6 +834,7 @@ const createConfigItem = (
788
834
  routeNames,
789
835
  parse,
790
836
  explicitParamNames: getExplicitParamNames(parse),
837
+ pathParamNames,
791
838
  hasNestedScreens,
792
839
  shared,
793
840
  };
@@ -1006,16 +1053,10 @@ const createNestedStateObject = (
1006
1053
 
1007
1054
  focusedRoute.path = path.replace(/\/$/, '');
1008
1055
 
1009
- const pathParamNames = new Set(
1010
- routeConfig?.params
1011
- .filter((param) => param.screen === routeConfig.screen)
1012
- .map((param) => param.name)
1013
- );
1014
-
1015
1056
  const queryParams = parseQueryParams(
1016
1057
  path,
1017
1058
  routeConfig?.parse,
1018
- pathParamNames,
1059
+ routeConfig?.pathParamNames,
1019
1060
  routeConfig?.explicitParamNames,
1020
1061
  routeConfig?.hasNestedScreens,
1021
1062
  focusedRoute.params
package/src/index.tsx CHANGED
@@ -4,6 +4,7 @@ export {
4
4
  createNavigatorFactory,
5
5
  type TypedNavigatorFactory,
6
6
  } from './createNavigatorFactory';
7
+ export { getLoaderForState as UNSTABLE_getLoaderForState } from './DataLoading';
7
8
  export { findFocusedRoute } from './findFocusedRoute';
8
9
  export { getActionFromState } from './getActionFromState';
9
10
  export { getFocusedRouteNameFromRoute } from './getFocusedRouteNameFromRoute';
@@ -59,6 +60,7 @@ export type {
59
60
  NavigatorScreenParams,
60
61
  NavigatorTypeBagBase,
61
62
  NavigatorTypeBagFor,
63
+ NestedNavigatorsForParamList,
62
64
  PathConfig,
63
65
  PathConfigMap,
64
66
  RootNavigator,
package/src/types.tsx CHANGED
@@ -429,7 +429,7 @@ type NavigationHelpersCommon<
429
429
 
430
430
  type ParamType<
431
431
  ParamList extends {},
432
- RouteName extends keyof ParamList | unknown,
432
+ RouteName,
433
433
  IsPartial extends boolean = false,
434
434
  > = RouteName extends keyof ParamList
435
435
  ? ParamList[RouteName] extends undefined
@@ -724,16 +724,16 @@ export type RouteConfigComponent<
724
724
  * React component to render for this screen.
725
725
  */
726
726
  component: ScreenComponentType<ParamList, RouteName>;
727
- getComponent?: never | undefined;
728
- children?: never | undefined;
727
+ getComponent?: never;
728
+ children?: never;
729
729
  }
730
730
  | {
731
731
  /**
732
732
  * Lazily get a React component to render for this screen.
733
733
  */
734
734
  getComponent: () => ScreenComponentType<ParamList, RouteName>;
735
- component?: never | undefined;
736
- children?: never | undefined;
735
+ component?: never;
736
+ children?: never;
737
737
  }
738
738
  | {
739
739
  /**
@@ -743,8 +743,8 @@ export type RouteConfigComponent<
743
743
  route: RouteProp<ParamList, RouteName>;
744
744
  navigation: any;
745
745
  }) => React.ReactNode;
746
- component?: never | undefined;
747
- getComponent?: never | undefined;
746
+ component?: never;
747
+ getComponent?: never;
748
748
  };
749
749
 
750
750
  export type RouteConfigProps<
@@ -1147,27 +1147,18 @@ type NavigationListForScreenParams<
1147
1147
  RouteName extends keyof ParamList,
1148
1148
  ExcludedRouteNames,
1149
1149
  Parent extends NavigationProp<any, any, any, any, any> | undefined,
1150
- > =
1151
- T extends PrivateValueStore<[any, any, any, any]>
1152
- ? T extends NavigatorLike
1153
- ? NavigationListForNestedInternal<
1154
- T,
1155
- GenericNavigationComposite<ParamList, RouteName, Parent>
1156
- >
1157
- : NavigationListForParamListScreenParams<
1158
- T,
1159
- ParamList,
1160
- RouteName,
1161
- ExcludedRouteNames,
1162
- Parent
1163
- >
1164
- : NavigationListForParamListScreenParams<
1165
- T,
1166
- ParamList,
1167
- RouteName,
1168
- ExcludedRouteNames,
1169
- Parent
1170
- >;
1150
+ > = T extends PrivateValueStore<[any, any, any, any]> & NavigatorLike
1151
+ ? NavigationListForNestedInternal<
1152
+ T,
1153
+ GenericNavigationComposite<ParamList, RouteName, Parent>
1154
+ >
1155
+ : NavigationListForParamListScreenParams<
1156
+ T,
1157
+ ParamList,
1158
+ RouteName,
1159
+ ExcludedRouteNames,
1160
+ Parent
1161
+ >;
1171
1162
 
1172
1163
  type NavigationListForParamListScreenParams<
1173
1164
  T extends {},
@@ -1435,20 +1426,39 @@ export type NavigatorTypeBagFor<
1435
1426
  ParamList extends {},
1436
1427
  > = TypeBag & { ParamList: ParamList };
1437
1428
 
1438
- type TypedNavigatorComponent<Bag extends NavigatorTypeBagBase> =
1429
+ export type NavigatorProps<
1430
+ ParamList extends ParamListBase,
1431
+ State extends NavigationState,
1432
+ ScreenOptions extends {},
1433
+ EventMap extends EventMapBase,
1434
+ Navigation,
1435
+ Navigator extends React.ComponentType<any>,
1436
+ > = Omit<
1437
+ React.ComponentProps<Navigator>,
1438
+ keyof DefaultNavigatorOptions<any, any, any, any, any>
1439
+ > &
1440
+ DefaultNavigatorOptions<
1441
+ ParamList,
1442
+ State,
1443
+ ScreenOptions,
1444
+ EventMap,
1445
+ Navigation
1446
+ >;
1447
+
1448
+ type TypedNavigatorStaticComponent<Bag extends NavigatorTypeBagBase> =
1439
1449
  React.ComponentType<
1440
- Omit<
1441
- React.ComponentProps<
1442
- TypedNavigatorInternal<
1450
+ Partial<
1451
+ Omit<
1452
+ NavigatorProps<
1443
1453
  Bag['ParamList'],
1444
1454
  Bag['State'],
1445
1455
  Bag['ScreenOptions'],
1446
1456
  Bag['EventMap'],
1447
- Bag['NavigationList'],
1457
+ Bag['NavigationList'][keyof Bag['ParamList']],
1448
1458
  Bag['Navigator']
1449
- >['Navigator']
1450
- >,
1451
- 'children'
1459
+ >,
1460
+ 'children'
1461
+ >
1452
1462
  >
1453
1463
  >;
1454
1464
 
@@ -1466,10 +1476,10 @@ type TypedNavigatorStatic<
1466
1476
  config: Config;
1467
1477
  with: (
1468
1478
  Component: React.ComponentType<{
1469
- Navigator: TypedNavigatorComponent<Bag>;
1479
+ Navigator: TypedNavigatorStaticComponent<Bag>;
1470
1480
  }>
1471
1481
  ) => TypedNavigatorStaticDecorated<Bag, Config>;
1472
- getComponent: () => TypedNavigatorComponent<Bag>;
1482
+ getComponent: () => TypedNavigatorStaticComponent<Bag>;
1473
1483
  };
1474
1484
 
1475
1485
  export type TypedNavigator<
@@ -1502,17 +1512,14 @@ type TypedNavigatorInternal<
1502
1512
  * Navigator component which manages the child screens.
1503
1513
  */
1504
1514
  Navigator: React.ComponentType<
1505
- Omit<
1506
- React.ComponentProps<Navigator>,
1507
- keyof DefaultNavigatorOptions<any, any, any, any, any>
1508
- > &
1509
- DefaultNavigatorOptions<
1510
- ParamList,
1511
- State,
1512
- ScreenOptions,
1513
- EventMap,
1514
- NavigationList[keyof ParamList]
1515
- >
1515
+ NavigatorProps<
1516
+ ParamList,
1517
+ State,
1518
+ ScreenOptions,
1519
+ EventMap,
1520
+ NavigationList[keyof ParamList],
1521
+ Navigator
1522
+ >
1516
1523
  >;
1517
1524
  /**
1518
1525
  * Component used for grouping multiple route configuration.
@@ -1632,7 +1639,12 @@ type PathConfigAlias<Params> = {
1632
1639
  };
1633
1640
 
1634
1641
  export type PathConfig<Params> = FlatType<
1635
- Partial<PathConfigAlias<Params>> & {
1642
+ Partial<Omit<PathConfigAlias<Params>, 'path'>> & {
1643
+ /**
1644
+ * Path string to match against.
1645
+ * e.g. `/users/:id` will match `/users/1` and extract `id` param as `1`.
1646
+ */
1647
+ path?: string | undefined;
1636
1648
  /**
1637
1649
  * An object mapping the param name to a function which converts the param value to a string.
1638
1650
  * By default, all params are converted to strings using `String(value)`.
@@ -19,7 +19,7 @@ export function useEventEmitter<T extends Record<string, any>>(
19
19
  const listenRef = React.useRef(listen);
20
20
  const onPostEmitRef = React.useRef(onPostEmit);
21
21
 
22
- React.useEffect(() => {
22
+ React.useInsertionEffect(() => {
23
23
  listenRef.current = listen;
24
24
  onPostEmitRef.current = onPostEmit;
25
25
  });
@@ -28,8 +28,10 @@ export function useKeyedChildListeners() {
28
28
  keyedListeners[type][key] = listener;
29
29
 
30
30
  return () => {
31
- // @ts-expect-error: according to ref stated above you can use `key` to index type
32
- keyedListeners[type][key] = undefined;
31
+ if (keyedListeners[type][key] === listener) {
32
+ // @ts-expect-error: according to ref stated above you can use `key` to index type
33
+ keyedListeners[type][key] = undefined;
34
+ }
33
35
  };
34
36
  },
35
37
  [keyedListeners]
@@ -1,10 +1,7 @@
1
1
  import * as React from 'react';
2
2
 
3
3
  import { NavigationContainerRefContext } from './NavigationContainerRefContext';
4
- import {
5
- NavigationContext,
6
- NavigationRouteNameContext,
7
- } from './NavigationProvider';
4
+ import { NavigationContext } from './NavigationProvider';
8
5
  import type {
9
6
  GenericNavigation,
10
7
  NavigationListForNavigator,
@@ -46,23 +43,7 @@ export function useNavigation(name?: string): unknown {
46
43
  return navigation ?? root;
47
44
  }
48
45
 
49
- const routeName = React.use(NavigationRouteNameContext);
50
-
51
- // Generally, this condition isn't needed as `getParent(name)` works for current screen
52
- // However, it'll throw if the hook is used in a preloaded screen
53
- if (routeName === name) {
54
- if (navigation === undefined) {
55
- throw new Error(
56
- `Couldn't find a navigation object for '${name}'. This is not expected.`
57
- );
58
- }
59
-
60
- return navigation;
61
- }
62
-
63
- // We get parent first so that `getParent(name)` is not called in preloaded screens
64
- // It's ok since `getParent` also works for current screen, so parent is not skipped
65
- const parent = navigation?.getParent()?.getParent(name);
46
+ const parent = navigation?.getParent(name);
66
47
 
67
48
  if (parent === undefined) {
68
49
  throw new Error(
@@ -583,7 +583,7 @@ export function useNavigationBuilder<
583
583
 
584
584
  const previousRouteKeyListRef = React.useRef(routeKeyList);
585
585
 
586
- React.useEffect(() => {
586
+ React.useInsertionEffect(() => {
587
587
  previousRouteKeyListRef.current = routeKeyList;
588
588
  });
589
589
 
@@ -789,19 +789,6 @@ export function useNavigationBuilder<
789
789
  // eslint-disable-next-line react-hooks/exhaustive-deps
790
790
  }, []);
791
791
 
792
- // In some cases (e.g. route names change), internal state might have changed
793
- // But it hasn't been committed yet, so hasn't propagated to the sync external store
794
- // During this time, we need to return the internal state in `getState`
795
- // Otherwise it can result in inconsistent state during render in children
796
- // To avoid this, we use a ref for render phase, and immediately clear it on commit
797
- const stateRef = React.useRef<State | null>(state);
798
-
799
- stateRef.current = state;
800
-
801
- React.useLayoutEffect(() => {
802
- stateRef.current = null;
803
- });
804
-
805
792
  const getState = useLatestCallback((): State => {
806
793
  const currentState = getCurrentState();
807
794
 
@@ -956,9 +943,9 @@ export function useNavigationBuilder<
956
943
  onAction,
957
944
  onUnhandledAction,
958
945
  getState,
946
+ state,
959
947
  emitter,
960
948
  router,
961
- stateRef,
962
949
  });
963
950
 
964
951
  useFocusedListenersChildrenAdapter({
@@ -1013,7 +1000,10 @@ export function useNavigationBuilder<
1013
1000
  return (
1014
1001
  <NavigationMetaContext.Provider value={undefined}>
1015
1002
  <NavigationHelpersContext.Provider value={navigation}>
1016
- <NavigationStateListenerProvider state={state}>
1003
+ <NavigationStateListenerProvider
1004
+ state={state}
1005
+ getState={navigation.getState}
1006
+ >
1017
1007
  <FocusedRouteKeyContext.Provider value={focusedRoute.key}>
1018
1008
  <PreventRemoveProvider>{element}</PreventRemoveProvider>
1019
1009
  </FocusedRouteKeyContext.Provider>
@@ -91,27 +91,15 @@ export function useNavigationCache<
91
91
  [getState, parentNavigationHelpers, navigation, setOptions, emitter]
92
92
  );
93
93
 
94
- cache.current = routes.reduce<
94
+ const next = routes.reduce<
95
95
  NavigationCache<State, ScreenOptions, EventMap, ActionHelpers>
96
96
  >((acc, route) => {
97
97
  const previous = cache.current[route.key];
98
98
 
99
- type Thunk =
100
- | NavigationAction
101
- | ((state: State) => NavigationAction | null | undefined);
102
-
103
99
  if (previous) {
104
100
  // If a cached navigation object already exists, reuse it
105
101
  acc[route.key] = previous;
106
102
  } else {
107
- const dispatch = (thunk: Thunk) => {
108
- const action = typeof thunk === 'function' ? thunk(getState()) : thunk;
109
-
110
- if (action != null) {
111
- navigation.dispatch({ source: route.key, ...action });
112
- }
113
- };
114
-
115
103
  const withStack = (callback: () => void) => {
116
104
  let isStackSet = false;
117
105
 
@@ -134,6 +122,23 @@ export function useNavigationCache<
134
122
  }
135
123
  };
136
124
 
125
+ const dispatch = (
126
+ thunk:
127
+ | NavigationAction
128
+ | ((state: State) => NavigationAction | null | undefined)
129
+ ) => {
130
+ withStack(() =>
131
+ React.startTransition(() => {
132
+ const action =
133
+ typeof thunk === 'function' ? thunk(getState()) : thunk;
134
+
135
+ if (action != null) {
136
+ navigation.dispatch({ source: route.key, ...action });
137
+ }
138
+ })
139
+ );
140
+ };
141
+
137
142
  const actions = {
138
143
  ...router.actionCreators,
139
144
  ...CommonActions,
@@ -142,10 +147,8 @@ export function useNavigationCache<
142
147
  const helpers = Object.keys(actions).reduce<Record<string, () => void>>(
143
148
  (acc, name) => {
144
149
  acc[name] = (...args: any) =>
145
- withStack(() =>
146
- // @ts-expect-error: name is a valid key, but TypeScript is dumb
147
- dispatch(actions[name](...args))
148
- );
150
+ // @ts-expect-error: name is a valid key, but TypeScript is dumb
151
+ dispatch(actions[name](...args));
149
152
 
150
153
  return acc;
151
154
  },
@@ -160,7 +163,7 @@ export function useNavigationCache<
160
163
  ...helpers,
161
164
  // FIXME: too much work to fix the types for now
162
165
  ...(emitter.create(route.key) as any),
163
- dispatch: (thunk: Thunk) => withStack(() => dispatch(thunk)),
166
+ dispatch,
164
167
  getParent: (routeName) => {
165
168
  if (routeName === route.name) {
166
169
  // If the passed route name is the same as the current route's name,
@@ -197,5 +200,9 @@ export function useNavigationCache<
197
200
  return acc;
198
201
  }, {});
199
202
 
200
- return cache.current;
203
+ React.useInsertionEffect(() => {
204
+ cache.current = next;
205
+ });
206
+
207
+ return next;
201
208
  }