@react-navigation/core 8.0.0-alpha.33 → 8.0.0-alpha.35

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/README.md +17 -14
  2. package/lib/module/BaseNavigationContainer.js +14 -2
  3. package/lib/module/BaseNavigationContainer.js.map +1 -1
  4. package/lib/module/NavigationBuilderContext.js +8 -13
  5. package/lib/module/NavigationBuilderContext.js.map +1 -1
  6. package/lib/module/NavigationIndependentTree.js +45 -15
  7. package/lib/module/NavigationIndependentTree.js.map +1 -1
  8. package/lib/module/NavigationProvider.js +9 -4
  9. package/lib/module/NavigationProvider.js.map +1 -1
  10. package/lib/module/PreventRemoveProvider.js +2 -2
  11. package/lib/module/PreventRemoveProvider.js.map +1 -1
  12. package/lib/module/SceneView.js +2 -1
  13. package/lib/module/SceneView.js.map +1 -1
  14. package/lib/module/getPathFromState.js +41 -16
  15. package/lib/module/getPathFromState.js.map +1 -1
  16. package/lib/module/getPatternParts.js +43 -12
  17. package/lib/module/getPatternParts.js.map +1 -1
  18. package/lib/module/getStateFromPath.js +175 -109
  19. package/lib/module/getStateFromPath.js.map +1 -1
  20. package/lib/module/index.js +1 -0
  21. package/lib/module/index.js.map +1 -1
  22. package/lib/module/useDescriptors.js +9 -6
  23. package/lib/module/useDescriptors.js.map +1 -1
  24. package/lib/module/useFocusEffect.js +4 -2
  25. package/lib/module/useFocusEffect.js.map +1 -1
  26. package/lib/module/useFocusEvents.js +4 -2
  27. package/lib/module/useFocusEvents.js.map +1 -1
  28. package/lib/module/useFocusedListenersChildrenAdapter.js +2 -2
  29. package/lib/module/useFocusedListenersChildrenAdapter.js.map +1 -1
  30. package/lib/module/useIsFocused.js +1 -0
  31. package/lib/module/useIsFocused.js.map +1 -1
  32. package/lib/module/useNavigationBuilder.js +42 -39
  33. package/lib/module/useNavigationBuilder.js.map +1 -1
  34. package/lib/module/useNavigationCache.js +85 -81
  35. package/lib/module/useNavigationCache.js.map +1 -1
  36. package/lib/module/useNavigationHelpers.js +2 -2
  37. package/lib/module/useNavigationHelpers.js.map +1 -1
  38. package/lib/module/useNavigationState.js +48 -23
  39. package/lib/module/useNavigationState.js.map +1 -1
  40. package/lib/module/useOnAction.js +6 -4
  41. package/lib/module/useOnAction.js.map +1 -1
  42. package/lib/module/useOnGetNavigation.js +2 -2
  43. package/lib/module/useOnGetNavigation.js.map +1 -1
  44. package/lib/module/useOnGetState.js +2 -2
  45. package/lib/module/useOnGetState.js.map +1 -1
  46. package/lib/module/useOnPreventRemove.js +2 -2
  47. package/lib/module/useOnPreventRemove.js.map +1 -1
  48. package/lib/module/useOnRouteFocus.js +2 -2
  49. package/lib/module/useOnRouteFocus.js.map +1 -1
  50. package/lib/module/useOptionsGetters.js +19 -20
  51. package/lib/module/useOptionsGetters.js.map +1 -1
  52. package/lib/module/usePreventRemoveContext.js +1 -1
  53. package/lib/module/usePreventRemoveContext.js.map +1 -1
  54. package/lib/module/useRouteCache.js +43 -33
  55. package/lib/module/useRouteCache.js.map +1 -1
  56. package/lib/module/useScheduleUpdate.js +5 -3
  57. package/lib/module/useScheduleUpdate.js.map +1 -1
  58. package/lib/module/useSyncState.js +9 -10
  59. package/lib/module/useSyncState.js.map +1 -1
  60. package/lib/typescript/src/BaseNavigationContainer.d.ts.map +1 -1
  61. package/lib/typescript/src/NavigationBuilderContext.d.ts +9 -6
  62. package/lib/typescript/src/NavigationBuilderContext.d.ts.map +1 -1
  63. package/lib/typescript/src/NavigationIndependentTree.d.ts.map +1 -1
  64. package/lib/typescript/src/NavigationProvider.d.ts.map +1 -1
  65. package/lib/typescript/src/PreventRemoveProvider.d.ts.map +1 -1
  66. package/lib/typescript/src/SceneView.d.ts +2 -1
  67. package/lib/typescript/src/SceneView.d.ts.map +1 -1
  68. package/lib/typescript/src/getPathFromState.d.ts.map +1 -1
  69. package/lib/typescript/src/getPatternParts.d.ts +4 -2
  70. package/lib/typescript/src/getPatternParts.d.ts.map +1 -1
  71. package/lib/typescript/src/getStateFromPath.d.ts.map +1 -1
  72. package/lib/typescript/src/index.d.ts +1 -0
  73. package/lib/typescript/src/index.d.ts.map +1 -1
  74. package/lib/typescript/src/types.d.ts +1 -1
  75. package/lib/typescript/src/types.d.ts.map +1 -1
  76. package/lib/typescript/src/useDescriptors.d.ts +2 -2
  77. package/lib/typescript/src/useDescriptors.d.ts.map +1 -1
  78. package/lib/typescript/src/useFocusEffect.d.ts.map +1 -1
  79. package/lib/typescript/src/useFocusEvents.d.ts.map +1 -1
  80. package/lib/typescript/src/useIsFocused.d.ts +1 -0
  81. package/lib/typescript/src/useIsFocused.d.ts.map +1 -1
  82. package/lib/typescript/src/useNavigationBuilder.d.ts +1 -3
  83. package/lib/typescript/src/useNavigationBuilder.d.ts.map +1 -1
  84. package/lib/typescript/src/useNavigationCache.d.ts.map +1 -1
  85. package/lib/typescript/src/useNavigationState.d.ts +1 -0
  86. package/lib/typescript/src/useNavigationState.d.ts.map +1 -1
  87. package/lib/typescript/src/useOnAction.d.ts.map +1 -1
  88. package/lib/typescript/src/useOnGetNavigation.d.ts.map +1 -1
  89. package/lib/typescript/src/useOnGetState.d.ts.map +1 -1
  90. package/lib/typescript/src/useOnPreventRemove.d.ts.map +1 -1
  91. package/lib/typescript/src/useOnRouteFocus.d.ts.map +1 -1
  92. package/lib/typescript/src/useOptionsGetters.d.ts +2 -4
  93. package/lib/typescript/src/useOptionsGetters.d.ts.map +1 -1
  94. package/lib/typescript/src/useRouteCache.d.ts +7 -12
  95. package/lib/typescript/src/useRouteCache.d.ts.map +1 -1
  96. package/lib/typescript/src/useScheduleUpdate.d.ts.map +1 -1
  97. package/lib/typescript/src/useSyncState.d.ts.map +1 -1
  98. package/lib/typescript/src/utilities.d.ts +3 -0
  99. package/lib/typescript/src/utilities.d.ts.map +1 -1
  100. package/package.json +2 -3
  101. package/src/BaseNavigationContainer.tsx +19 -2
  102. package/src/NavigationBuilderContext.tsx +22 -18
  103. package/src/NavigationIndependentTree.tsx +56 -22
  104. package/src/NavigationProvider.tsx +13 -4
  105. package/src/PreventRemoveProvider.tsx +2 -8
  106. package/src/SceneView.tsx +3 -1
  107. package/src/getPathFromState.tsx +63 -15
  108. package/src/getPatternParts.tsx +75 -14
  109. package/src/getStateFromPath.tsx +250 -113
  110. package/src/index.tsx +1 -0
  111. package/src/types.tsx +1 -1
  112. package/src/useDescriptors.tsx +20 -6
  113. package/src/useFocusEffect.tsx +5 -2
  114. package/src/useFocusEvents.tsx +5 -2
  115. package/src/useFocusedListenersChildrenAdapter.tsx +2 -2
  116. package/src/useIsFocused.tsx +4 -0
  117. package/src/useNavigationBuilder.tsx +54 -48
  118. package/src/useNavigationCache.tsx +130 -108
  119. package/src/useNavigationHelpers.tsx +2 -2
  120. package/src/useNavigationState.tsx +60 -29
  121. package/src/useOnAction.tsx +6 -5
  122. package/src/useOnGetNavigation.tsx +3 -2
  123. package/src/useOnGetState.tsx +4 -2
  124. package/src/useOnPreventRemove.tsx +4 -2
  125. package/src/useOnRouteFocus.tsx +2 -4
  126. package/src/useOptionsGetters.tsx +26 -25
  127. package/src/usePreventRemoveContext.tsx +1 -1
  128. package/src/useRouteCache.tsx +49 -33
  129. package/src/useScheduleUpdate.tsx +6 -3
  130. package/src/useSyncState.tsx +9 -11
  131. package/src/utilities.tsx +5 -2
  132. package/lib/module/useComponent.js +0 -34
  133. package/lib/module/useComponent.js.map +0 -1
  134. package/lib/typescript/src/useComponent.d.ts +0 -7
  135. package/lib/typescript/src/useComponent.d.ts.map +0 -1
  136. package/src/useComponent.tsx +0 -37
@@ -1,5 +1,6 @@
1
1
  import * as React from 'react';
2
2
 
3
+ import { IsFocusedGetterContext } from './useIsFocused';
3
4
  import { useNavigation } from './useNavigation';
4
5
 
5
6
  type EffectCallback = () => undefined | void | (() => void);
@@ -14,6 +15,8 @@ type EffectCallback = () => undefined | void | (() => void);
14
15
  export function useFocusEffect(effect: EffectCallback) {
15
16
  const navigation = useNavigation();
16
17
 
18
+ const getIsFocused = React.use(IsFocusedGetterContext);
19
+
17
20
  // eslint-disable-next-line prefer-rest-params
18
21
  if (arguments[1] !== undefined) {
19
22
  const message =
@@ -72,7 +75,7 @@ export function useFocusEffect(effect: EffectCallback) {
72
75
  };
73
76
 
74
77
  // We need to run the effect on initial render/dep changes if the screen is focused
75
- if (navigation.isFocused()) {
78
+ if (getIsFocused?.() ?? true) {
76
79
  cleanup = callback();
77
80
  isFocused = true;
78
81
  }
@@ -109,5 +112,5 @@ export function useFocusEffect(effect: EffectCallback) {
109
112
  unsubscribeFocus();
110
113
  unsubscribeBlur();
111
114
  };
112
- }, [effect, navigation]);
115
+ }, [effect, navigation, getIsFocused]);
113
116
  }
@@ -4,6 +4,7 @@ import * as React from 'react';
4
4
  import { NavigationContext } from './NavigationProvider';
5
5
  import type { EventMapCore } from './types';
6
6
  import type { NavigationEventEmitter } from './useEventEmitter';
7
+ import { IsFocusedGetterContext } from './useIsFocused';
7
8
 
8
9
  type Options<State extends NavigationState> = {
9
10
  state: State;
@@ -18,6 +19,8 @@ export function useFocusEvents<State extends NavigationState>({
18
19
  emitter,
19
20
  }: Options<State>) {
20
21
  const navigation = React.use(NavigationContext);
22
+ const getIsFocused = React.use(IsFocusedGetterContext);
23
+
21
24
  const lastFocusedKeyRef = React.useRef<string | undefined>(undefined);
22
25
 
23
26
  const currentFocusedRoute = state.routes[state.index];
@@ -51,7 +54,7 @@ export function useFocusEvents<State extends NavigationState>({
51
54
  React.useEffect(() => {
52
55
  const lastFocusedKey = lastFocusedKeyRef.current;
53
56
 
54
- const isFocused = navigation ? navigation.isFocused() : true;
57
+ const isFocused = getIsFocused?.() ?? true;
55
58
 
56
59
  if (isFocused) {
57
60
  // Remember the current route as focused if the navigator is focused
@@ -77,5 +80,5 @@ export function useFocusEvents<State extends NavigationState>({
77
80
 
78
81
  emitter.emit({ type: 'blur', target: lastFocusedKey });
79
82
  emitter.emit({ type: 'focus', target: currentFocusedKey });
80
- }, [currentFocusedKey, emitter, navigation]);
83
+ }, [currentFocusedKey, emitter, navigation, getIsFocused]);
81
84
  }
@@ -4,7 +4,7 @@ import * as React from 'react';
4
4
  import {
5
5
  type FocusedNavigationCallback,
6
6
  type FocusedNavigationListener,
7
- NavigationBuilderContext,
7
+ useNavigationBuilderContext,
8
8
  } from './NavigationBuilderContext';
9
9
  import type { NavigationHelpers } from './types';
10
10
 
@@ -20,7 +20,7 @@ export function useFocusedListenersChildrenAdapter({
20
20
  navigation,
21
21
  focusedListeners,
22
22
  }: Options) {
23
- const { addListener } = React.use(NavigationBuilderContext);
23
+ const { addListener } = useNavigationBuilderContext();
24
24
 
25
25
  const listener = React.useCallback(
26
26
  (callback: FocusedNavigationCallback<any>) => {
@@ -8,6 +8,10 @@ export const IsFocusedContext = React.createContext<boolean | undefined>(
8
8
  undefined
9
9
  );
10
10
 
11
+ export const IsFocusedGetterContext = React.createContext<
12
+ (() => boolean) | undefined
13
+ >(undefined);
14
+
11
15
  /**
12
16
  * Hook to get the current focus state of the screen. Returns a `true` if screen is focused, otherwise `false`.
13
17
  * This can be used if a component needs to render something based on the focus state.
@@ -19,7 +19,7 @@ import { ConsumedParamsContext } from './ConsumedParamsContext';
19
19
  import { deepFreeze } from './deepFreeze';
20
20
  import { Group } from './Group';
21
21
  import { isArrayEqual } from './isArrayEqual';
22
- import { NavigationBuilderContext } from './NavigationBuilderContext';
22
+ import { useNavigationBuilderContext } from './NavigationBuilderContext';
23
23
  import { NavigationHelpersContext } from './NavigationHelpersContext';
24
24
  import { NavigationMetaContext } from './NavigationMetaContext';
25
25
  import { IsScreenContext, NavigationRouteContext } from './NavigationProvider';
@@ -36,7 +36,6 @@ import {
36
36
  } from './types';
37
37
  import { UnhandledActionContext } from './UnhandledActionContext';
38
38
  import { useChildListeners } from './useChildListeners';
39
- import { useComponent } from './useComponent';
40
39
  import { type ScreenConfigWithParent, useDescriptors } from './useDescriptors';
41
40
  import { useEventEmitter } from './useEventEmitter';
42
41
  import { useFocusedListenersChildrenAdapter } from './useFocusedListenersChildrenAdapter';
@@ -460,20 +459,22 @@ export function useNavigationBuilder<
460
459
  getIsInitial,
461
460
  } = React.use(NavigationStateContext);
462
461
 
463
- const { onEmitEvent, getIsStateEmitted } = React.use(
464
- NavigationBuilderContext
462
+ const { onEmitEvent, getIsStateEmitted } = useNavigationBuilderContext();
463
+
464
+ const mountStateRef = React.useRef<'initial' | 'mounted' | 'unmounted'>(
465
+ 'initial'
465
466
  );
466
467
 
467
- const stateCleanupRef = React.useRef<boolean>(false);
468
- const lastStateRef = React.useRef<State | PartialState<State> | undefined>(
468
+ const previousStateRef = React.useRef(currentState);
469
+ const savedStateRef = React.useRef<State | PartialState<State> | undefined>(
469
470
  undefined
470
471
  );
471
472
 
472
473
  const setState = useLatestCallback(
473
474
  (state: State | PartialState<State> | undefined) => {
474
- if (stateCleanupRef.current) {
475
+ if (mountStateRef.current === 'unmounted') {
475
476
  // Store the state locally in case the current navigator is in `Activity`
476
- lastStateRef.current = state;
477
+ savedStateRef.current = state;
477
478
 
478
479
  // State might have been already cleaned up due to unmount
479
480
  // We don't want to update `route.state` in parent
@@ -485,6 +486,19 @@ export function useNavigationBuilder<
485
486
  }
486
487
  );
487
488
 
489
+ const lastState =
490
+ (mountStateRef.current !== 'mounted' ||
491
+ !isStateInitialized(currentState)) &&
492
+ // If the state object differs from previously committed state, we have new state
493
+ // e.g. because of a reset
494
+ // So we need to use it before checking if we should reuse the saved state.
495
+ (currentState === previousStateRef.current ||
496
+ (mountStateRef.current === 'unmounted' && currentState === undefined))
497
+ ? // If the navigator is hidden with `Activity`, its state gets cleaned up due to effect cleanup.
498
+ // So we reuse the saved state to avoid remounting screens when the navigator is shown again.
499
+ savedStateRef.current
500
+ : undefined;
501
+
488
502
  const [
489
503
  stateBeforeInitialization,
490
504
  initializedState,
@@ -496,17 +510,10 @@ export function useNavigationBuilder<
496
510
  boolean,
497
511
  object | undefined,
498
512
  ] => {
499
- // If the state was already cleaned up, but we have it stored in ref,
500
- // It likely got cleaned up due to `<Activity mode="hidden">`
501
- // We should reuse this state to avoid remounting screens
502
- if (
503
- stateCleanupRef.current &&
504
- lastStateRef.current &&
505
- isStateValid(lastStateRef.current)
506
- ) {
507
- const state: State = isStateInitialized(lastStateRef.current)
508
- ? lastStateRef.current
509
- : router.getRehydratedState(lastStateRef.current, {
513
+ if (lastState && isStateValid(lastState)) {
514
+ const state: State = isStateInitialized(lastState)
515
+ ? lastState
516
+ : router.getRehydratedState(lastState, {
510
517
  routeNames,
511
518
  routeParamList,
512
519
  routeGetIdList,
@@ -607,7 +614,7 @@ export function useNavigationBuilder<
607
614
  // that some changes to routeConfigs are explicitly ignored, such as changes
608
615
  // to initialParams
609
616
  // eslint-disable-next-line react-hooks/exhaustive-deps
610
- }, [currentState, router, isStateValid]);
617
+ }, [currentState, lastState, router, isStateValid]);
611
618
 
612
619
  const previousRouteKeyListRef = React.useRef(routeKeyList);
613
620
 
@@ -647,7 +654,7 @@ export function useNavigationBuilder<
647
654
  // If the state isn't initialized, or stale, use the state we initialized instead
648
655
  // The state won't update until there's a change needed in the state we have initialized locally
649
656
  // So it'll be `undefined` or stale until the first navigation event happens
650
- isStateInitialized(currentState)
657
+ lastState === undefined && isStateInitialized(currentState)
651
658
  ? (currentState as State)
652
659
  : (initializedState as State);
653
660
 
@@ -772,7 +779,7 @@ export function useNavigationBuilder<
772
779
  : nextState;
773
780
  }
774
781
 
775
- React.useEffect(() => {
782
+ React.useInsertionEffect(() => {
776
783
  if (
777
784
  consumedParams &&
778
785
  didConsumeNestedParams &&
@@ -801,24 +808,29 @@ export function useNavigationBuilder<
801
808
  // So we override the state object we return to use the latest state as soon as possible
802
809
  state = nextState;
803
810
 
804
- // Last state to reuse if component gets cleaned up due to `<Activity mode="hidden">`
805
- React.useEffect(() => {
806
- lastStateRef.current = state;
811
+ React.useInsertionEffect(() => {
812
+ savedStateRef.current = state;
813
+ previousStateRef.current = currentState;
807
814
  });
808
815
 
809
- const lastNotifiedStateRef = React.useRef<State | null>(null);
816
+ React.useInsertionEffect(() => {
817
+ return () => {
818
+ // An initially hidden Activity doesn't run passive effects or their cleanup.
819
+ // We mark actual unmount here so queued updates can't write state into a replacement navigator.
820
+ mountStateRef.current = 'unmounted';
821
+ };
822
+ }, []);
810
823
 
811
824
  React.useEffect(() => {
825
+ const savedState = savedStateRef.current ?? state;
826
+
812
827
  // In strict mode, React will double-invoke effects.
813
- // So we need to reset the flag if component was not unmounted
814
- stateCleanupRef.current = false;
828
+ // So we need to reset the status if component was not unmounted
829
+ mountStateRef.current = 'mounted';
815
830
 
816
831
  setKey(navigatorKey);
817
832
 
818
- if (
819
- (!getIsInitial() || getIsStateEmitted()) &&
820
- lastNotifiedStateRef.current !== state
821
- ) {
833
+ if (!getIsInitial() || getIsStateEmitted()) {
822
834
  // We need to notify the state update in these scenarios:
823
835
  // 1. If it's not the initial render of the component containing the navigator
824
836
  // 2. If the container has already emitted state before the navigator mounted
@@ -831,31 +843,25 @@ export function useNavigationBuilder<
831
843
  // So only checking for the initial render is not enough.
832
844
  // We also need to propagate the navigator's initial state when it mounts later,
833
845
  // so the container is notified that the nested state is now available.
834
- //
835
- // We only notify if the state is different from what we already notified
836
- // Otherwise, this goes into a loop when inside `<Activity mode="hidden">`
837
- setState(state);
838
-
839
- lastNotifiedStateRef.current = state;
846
+ setState(savedState);
840
847
  }
841
848
 
842
849
  return () => {
843
850
  // We need to clean up state for this navigator on unmount
844
851
  if (getCurrentState() !== undefined && getKey() === navigatorKey) {
845
852
  setCurrentState(undefined);
846
- stateCleanupRef.current = true;
847
853
  }
848
854
 
849
- // Reset so that StrictMode's second mount re-propagates state correctly.
850
- // Without this, the guard above sees the same reference and skips setState,
851
- // causing the initial state to be lost after cleanup wipes the container state.
852
- lastNotifiedStateRef.current = null;
855
+ mountStateRef.current = 'unmounted';
853
856
  };
854
857
  // eslint-disable-next-line react-hooks/exhaustive-deps
855
858
  }, []);
856
859
 
857
860
  const getState = useLatestCallback((): State => {
858
- const currentState = getCurrentState();
861
+ const currentState =
862
+ mountStateRef.current === 'unmounted'
863
+ ? savedStateRef.current
864
+ : getCurrentState();
859
865
 
860
866
  return deepFreeze(
861
867
  (isStateInitialized(currentState)
@@ -1028,8 +1034,8 @@ export function useNavigationBuilder<
1028
1034
  ScreenOptions,
1029
1035
  EventMap
1030
1036
  >({
1031
- routes: state.routes,
1032
1037
  screens,
1038
+ state,
1033
1039
  navigation,
1034
1040
  screenOptions,
1035
1041
  screenLayout,
@@ -1045,7 +1051,7 @@ export function useNavigationBuilder<
1045
1051
  emitter,
1046
1052
  });
1047
1053
 
1048
- const NavigationContent = useComponent((children: React.ReactNode) => {
1054
+ const render = (children: React.ReactNode) => {
1049
1055
  const focusedRoute = state.routes[state.index];
1050
1056
 
1051
1057
  if (focusedRoute == null) {
@@ -1082,12 +1088,12 @@ export function useNavigationBuilder<
1082
1088
  </NavigationHelpersContext.Provider>
1083
1089
  </NavigationMetaContext.Provider>
1084
1090
  );
1085
- });
1091
+ };
1086
1092
 
1087
1093
  return {
1088
1094
  state,
1089
1095
  navigation,
1090
1096
  descriptors,
1091
- NavigationContent,
1097
+ render,
1092
1098
  };
1093
1099
  }
@@ -7,7 +7,7 @@ import {
7
7
  } from '@react-navigation/routers';
8
8
  import * as React from 'react';
9
9
 
10
- import { NavigationBuilderContext } from './NavigationBuilderContext';
10
+ import { useNavigationBuilderContext } from './NavigationBuilderContext';
11
11
  import { NavigationContext } from './NavigationProvider';
12
12
  import type { NavigationHelpers, NavigationProp } from './types';
13
13
  import type { NavigationEventEmitter } from './useEventEmitter';
@@ -72,128 +72,150 @@ export function useNavigationCache<
72
72
  router,
73
73
  emitter,
74
74
  }: Options<State, ScreenOptions, EventMap>) {
75
- const parentNavigation = React.use(NavigationContext);
75
+ const { withStackTrace } = useNavigationBuilderContext();
76
76
 
77
- const { withStackTrace } = React.use(NavigationBuilderContext);
77
+ const parentNavigation = React.use(NavigationContext);
78
78
 
79
79
  // Cache object which holds navigation objects for each screen
80
80
  // We use `React.useMemo` instead of `React.useRef` coz we want to invalidate it when deps change
81
81
  // In reality, these deps will rarely change, if ever
82
- const cache = React.useMemo(
83
- () => ({
84
- current: {} as NavigationCache<
85
- State,
86
- ScreenOptions,
87
- EventMap,
88
- ActionHelpers
89
- >,
90
- }),
82
+ const cache = React.useMemo<{
83
+ current: NavigationCache<State, ScreenOptions, EventMap, ActionHelpers>;
84
+ routeNames: Record<string, string>;
85
+ }>(
86
+ () => ({ current: {}, routeNames: {} }),
91
87
  // eslint-disable-next-line react-hooks/exhaustive-deps
92
- [getState, parentNavigation, navigation, setOptions, emitter]
88
+ [
89
+ getState,
90
+ parentNavigation,
91
+ navigation,
92
+ setOptions,
93
+ router,
94
+ emitter,
95
+ withStackTrace,
96
+ ]
93
97
  );
94
98
 
95
- const next = routes.reduce<
96
- NavigationCache<State, ScreenOptions, EventMap, ActionHelpers>
97
- >((acc, route) => {
98
- const previous = cache.current[route.key];
99
-
100
- if (previous) {
101
- // If a cached navigation object already exists, reuse it
102
- acc[route.key] = previous;
103
- } else {
104
- const dispatch = (
105
- thunk:
106
- | NavigationAction
107
- | ((state: State) => NavigationAction | null | undefined)
108
- ) => {
109
- withStackTrace(dispatch, () => {
110
- React.startTransition(() => {
111
- const action =
112
- typeof thunk === 'function' ? thunk(getState()) : thunk;
113
-
114
- if (action != null) {
115
- navigation.dispatch({ source: route.key, ...action });
116
- }
99
+ const next = React.useMemo(() => {
100
+ return routes.reduce<
101
+ NavigationCache<State, ScreenOptions, EventMap, ActionHelpers>
102
+ >((acc, route) => {
103
+ const previous =
104
+ cache.routeNames[route.key] === route.name
105
+ ? cache.current[route.key]
106
+ : undefined;
107
+
108
+ if (previous) {
109
+ // If a cached navigation object already exists, reuse it
110
+ acc[route.key] = previous;
111
+ } else {
112
+ const dispatch = (
113
+ thunk:
114
+ | NavigationAction
115
+ | ((state: State) => NavigationAction | null | undefined)
116
+ ) => {
117
+ withStackTrace(dispatch, () => {
118
+ React.startTransition(() => {
119
+ const action =
120
+ typeof thunk === 'function' ? thunk(getState()) : thunk;
121
+
122
+ if (action != null) {
123
+ navigation.dispatch({ source: route.key, ...action });
124
+ }
125
+ });
117
126
  });
118
- });
119
- };
120
-
121
- const actions = {
122
- ...router.actionCreators,
123
- ...CommonActions,
124
- };
125
-
126
- const helpers = Object.keys(actions).reduce<Record<string, () => void>>(
127
- (acc, name) => {
128
- const helper = (...args: any) =>
129
- withStackTrace(helper, () =>
130
- // @ts-expect-error: name is a valid key, but TypeScript is dumb
131
- dispatch(actions[name](...args))
132
- );
133
-
134
- acc[name] = helper;
135
-
136
- return acc;
137
- },
138
- {}
139
- );
140
-
141
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
142
- const { emit, ...rest } = navigation;
143
-
144
- acc[route.key] = {
145
- ...rest,
146
- ...helpers,
147
- // FIXME: too much work to fix the types for now
148
- ...(emitter.create(route.key) as any),
149
- dispatch,
150
- getParent: (routeName) => {
151
- if (routeName === route.name) {
152
- // If the passed route name is the same as the current route's name,
153
- // we return the cached navigation object for the route
154
- return acc[route.key];
155
- }
156
-
157
- if (routeName !== undefined) {
158
- const parent = parentNavigation?.getParent(routeName);
159
-
160
- if (parent === undefined) {
161
- throw new Error(
162
- `Couldn't find a navigation object for '${routeName}' in current or any parent screens. Is your component inside the correct screen?`
127
+ };
128
+
129
+ const actions = {
130
+ ...router.actionCreators,
131
+ ...CommonActions,
132
+ };
133
+
134
+ const helpers = Object.keys(actions).reduce<Record<string, () => void>>(
135
+ (acc, name) => {
136
+ const helper = (...args: any) =>
137
+ withStackTrace(helper, () =>
138
+ // @ts-expect-error: name is a valid key, but TypeScript is dumb
139
+ dispatch(actions[name](...args))
163
140
  );
141
+
142
+ acc[name] = helper;
143
+
144
+ return acc;
145
+ },
146
+ {}
147
+ );
148
+
149
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
150
+ const { emit, ...rest } = navigation;
151
+
152
+ acc[route.key] = {
153
+ ...rest,
154
+ ...helpers,
155
+ // FIXME: too much work to fix the types for now
156
+ ...(emitter.create(route.key) as any),
157
+ dispatch,
158
+ getParent: (routeName) => {
159
+ if (routeName === route.name) {
160
+ // If the passed route name is the same as the current route's name,
161
+ // we return the cached navigation object for the route
162
+ return acc[route.key];
163
+ }
164
+
165
+ if (routeName !== undefined) {
166
+ const parent = parentNavigation?.getParent(routeName);
167
+
168
+ if (parent === undefined) {
169
+ throw new Error(
170
+ `Couldn't find a navigation object for '${routeName}' in current or any parent screens. Is your component inside the correct screen?`
171
+ );
172
+ }
173
+
174
+ return parent;
175
+ }
176
+
177
+ return parentNavigation;
178
+ },
179
+ setOptions: (options: Partial<ScreenOptions>) => {
180
+ setOptions((o) => ({
181
+ ...o,
182
+ [route.key]: { ...o[route.key], ...options },
183
+ }));
184
+ },
185
+ isFocused: () => {
186
+ const state = rest.getState();
187
+
188
+ if (state.routes[state.index]?.key !== route.key) {
189
+ return false;
164
190
  }
165
191
 
166
- return parent;
167
- }
168
-
169
- return parentNavigation;
170
- },
171
- setOptions: (options: Partial<ScreenOptions>) => {
172
- setOptions((o) => ({
173
- ...o,
174
- [route.key]: { ...o[route.key], ...options },
175
- }));
176
- },
177
- isFocused: () => {
178
- const state = rest.getState();
179
-
180
- if (state.routes[state.index]?.key !== route.key) {
181
- return false;
182
- }
183
-
184
- // If the current screen is focused, we also need to check if parent navigator is focused
185
- // This makes sure that we return the focus state in the whole tree, not just this navigator
186
- return navigation ? navigation.isFocused() : true;
187
- },
188
- };
189
- }
190
-
191
- return acc;
192
- }, {});
192
+ // If the current screen is focused, we also need to check if parent navigator is focused
193
+ // This makes sure that we return the focus state in the whole tree, not just this navigator
194
+ return navigation ? navigation.isFocused() : true;
195
+ },
196
+ };
197
+ }
198
+
199
+ return acc;
200
+ }, {});
201
+ }, [
202
+ cache,
203
+ routes,
204
+ getState,
205
+ navigation,
206
+ router,
207
+ withStackTrace,
208
+ emitter,
209
+ parentNavigation,
210
+ setOptions,
211
+ ]);
193
212
 
194
213
  React.useInsertionEffect(() => {
195
214
  cache.current = next;
196
- });
215
+ cache.routeNames = Object.fromEntries(
216
+ routes.map((route) => [route.key, route.name])
217
+ );
218
+ }, [cache, next, routes]);
197
219
 
198
220
  return next;
199
221
  }
@@ -7,7 +7,7 @@ import {
7
7
  } from '@react-navigation/routers';
8
8
  import * as React from 'react';
9
9
 
10
- import { NavigationBuilderContext } from './NavigationBuilderContext';
10
+ import { useNavigationBuilderContext } from './NavigationBuilderContext';
11
11
  import { NavigationContext } from './NavigationProvider';
12
12
  import { type NavigationHelpers, PrivateValueStore } from './types';
13
13
  import type { NavigationEventEmitter } from './useEventEmitter';
@@ -41,7 +41,7 @@ export function useNavigationHelpers<
41
41
  router,
42
42
  }: Options<State, Action>) {
43
43
  const parentNavigationHelpers = React.use(NavigationContext);
44
- const { withStackTrace } = React.use(NavigationBuilderContext);
44
+ const { withStackTrace } = useNavigationBuilderContext();
45
45
 
46
46
  return React.useMemo(() => {
47
47
  const dispatch = (op: Action | ((state: State) => Action)) => {