@react-navigation/core 8.0.0-alpha.34 → 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 (84) 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/NavigationIndependentTree.js +9 -6
  5. package/lib/module/NavigationIndependentTree.js.map +1 -1
  6. package/lib/module/NavigationProvider.js +9 -4
  7. package/lib/module/NavigationProvider.js.map +1 -1
  8. package/lib/module/PreventRemoveProvider.js +2 -2
  9. package/lib/module/PreventRemoveProvider.js.map +1 -1
  10. package/lib/module/SceneView.js +2 -1
  11. package/lib/module/SceneView.js.map +1 -1
  12. package/lib/module/useDescriptors.js +7 -4
  13. package/lib/module/useDescriptors.js.map +1 -1
  14. package/lib/module/useFocusEffect.js +4 -2
  15. package/lib/module/useFocusEffect.js.map +1 -1
  16. package/lib/module/useFocusEvents.js +4 -2
  17. package/lib/module/useFocusEvents.js.map +1 -1
  18. package/lib/module/useIsFocused.js +1 -0
  19. package/lib/module/useIsFocused.js.map +1 -1
  20. package/lib/module/useNavigationBuilder.js +40 -37
  21. package/lib/module/useNavigationBuilder.js.map +1 -1
  22. package/lib/module/useNavigationCache.js +82 -78
  23. package/lib/module/useNavigationCache.js.map +1 -1
  24. package/lib/module/useNavigationState.js +48 -23
  25. package/lib/module/useNavigationState.js.map +1 -1
  26. package/lib/module/useOptionsGetters.js +18 -17
  27. package/lib/module/useOptionsGetters.js.map +1 -1
  28. package/lib/module/usePreventRemoveContext.js +1 -1
  29. package/lib/module/usePreventRemoveContext.js.map +1 -1
  30. package/lib/module/useRouteCache.js +43 -33
  31. package/lib/module/useRouteCache.js.map +1 -1
  32. package/lib/module/useScheduleUpdate.js +3 -1
  33. package/lib/module/useScheduleUpdate.js.map +1 -1
  34. package/lib/module/useSyncState.js +9 -10
  35. package/lib/module/useSyncState.js.map +1 -1
  36. package/lib/typescript/src/BaseNavigationContainer.d.ts.map +1 -1
  37. package/lib/typescript/src/NavigationBuilderContext.d.ts +1 -1
  38. package/lib/typescript/src/NavigationBuilderContext.d.ts.map +1 -1
  39. package/lib/typescript/src/NavigationIndependentTree.d.ts.map +1 -1
  40. package/lib/typescript/src/NavigationProvider.d.ts.map +1 -1
  41. package/lib/typescript/src/PreventRemoveProvider.d.ts.map +1 -1
  42. package/lib/typescript/src/SceneView.d.ts +2 -1
  43. package/lib/typescript/src/SceneView.d.ts.map +1 -1
  44. package/lib/typescript/src/useDescriptors.d.ts +2 -2
  45. package/lib/typescript/src/useDescriptors.d.ts.map +1 -1
  46. package/lib/typescript/src/useFocusEffect.d.ts.map +1 -1
  47. package/lib/typescript/src/useFocusEvents.d.ts.map +1 -1
  48. package/lib/typescript/src/useIsFocused.d.ts +1 -0
  49. package/lib/typescript/src/useIsFocused.d.ts.map +1 -1
  50. package/lib/typescript/src/useNavigationBuilder.d.ts +1 -3
  51. package/lib/typescript/src/useNavigationBuilder.d.ts.map +1 -1
  52. package/lib/typescript/src/useNavigationCache.d.ts.map +1 -1
  53. package/lib/typescript/src/useNavigationState.d.ts +1 -0
  54. package/lib/typescript/src/useNavigationState.d.ts.map +1 -1
  55. package/lib/typescript/src/useOptionsGetters.d.ts +2 -4
  56. package/lib/typescript/src/useOptionsGetters.d.ts.map +1 -1
  57. package/lib/typescript/src/useRouteCache.d.ts +7 -12
  58. package/lib/typescript/src/useRouteCache.d.ts.map +1 -1
  59. package/lib/typescript/src/useScheduleUpdate.d.ts.map +1 -1
  60. package/lib/typescript/src/useSyncState.d.ts.map +1 -1
  61. package/package.json +2 -2
  62. package/src/BaseNavigationContainer.tsx +19 -2
  63. package/src/NavigationBuilderContext.tsx +1 -1
  64. package/src/NavigationIndependentTree.tsx +16 -8
  65. package/src/NavigationProvider.tsx +13 -4
  66. package/src/PreventRemoveProvider.tsx +2 -8
  67. package/src/SceneView.tsx +3 -1
  68. package/src/useDescriptors.tsx +18 -5
  69. package/src/useFocusEffect.tsx +5 -2
  70. package/src/useFocusEvents.tsx +5 -2
  71. package/src/useIsFocused.tsx +4 -0
  72. package/src/useNavigationBuilder.tsx +53 -45
  73. package/src/useNavigationCache.tsx +127 -105
  74. package/src/useNavigationState.tsx +60 -29
  75. package/src/useOptionsGetters.tsx +24 -24
  76. package/src/usePreventRemoveContext.tsx +1 -1
  77. package/src/useRouteCache.tsx +49 -33
  78. package/src/useScheduleUpdate.tsx +4 -1
  79. package/src/useSyncState.tsx +9 -11
  80. package/lib/module/useComponent.js +0 -34
  81. package/lib/module/useComponent.js.map +0 -1
  82. package/lib/typescript/src/useComponent.d.ts +0 -7
  83. package/lib/typescript/src/useComponent.d.ts.map +0 -1
  84. package/src/useComponent.tsx +0 -37
@@ -1 +1 @@
1
- {"version":3,"file":"useRouteCache.d.ts","sourceRoot":"","sources":["../../../src/useRouteCache.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAiB,MAAM,2BAA2B,CAAC;AAMhF;;;;GAIG;AACH,eAAO,MAAM,WAAW,eAAwB,CAAC;AAEjD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,SAAS,eAAe,EACzD,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC;;;;;;;;;;KAsDxB"}
1
+ {"version":3,"file":"useRouteCache.d.ts","sourceRoot":"","sources":["../../../src/useRouteCache.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAIhF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAS,CAAC;AAEzC;;;;GAIG;AACH,eAAO,MAAM,WAAW,eAAwB,CAAC;AAEjD,KAAK,WAAW,GAAG,SAAS,CAAC,aAAa,CAAC,GAAG;IAC5C,CAAC,WAAW,CAAC,CAAC,EAAE,eAAe,CAAC;CACjC,CAAC;AAEF;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,SAAS,eAAe,EACzD,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,iBAkExB"}
@@ -1 +1 @@
1
- {"version":3,"file":"useScheduleUpdate.d.ts","sourceRoot":"","sources":["../../../src/useScheduleUpdate.tsx"],"names":[],"mappings":"AAIA;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,IAAI,QAQrD"}
1
+ {"version":3,"file":"useScheduleUpdate.d.ts","sourceRoot":"","sources":["../../../src/useScheduleUpdate.tsx"],"names":[],"mappings":"AAKA;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,IAAI,QAUrD"}
@@ -1 +1 @@
1
- {"version":3,"file":"useSyncState.d.ts","sourceRoot":"","sources":["../../../src/useSyncState.tsx"],"names":[],"mappings":"AAmFA,wBAAgB,YAAY,CAAC,CAAC,EAAE,eAAe,EAAE,MAAM,CAAC;;;;yCA/BnB,IAAI;wCA4Da,MAAM,IAAI;;EAiC/D"}
1
+ {"version":3,"file":"useSyncState.d.ts","sourceRoot":"","sources":["../../../src/useSyncState.tsx"],"names":[],"mappings":"AAmFA,wBAAgB,YAAY,CAAC,CAAC,EAAE,eAAe,EAAE,MAAM,CAAC;;;;yCA/BnB,IAAI;wCA4Da,MAAM,IAAI;;EA+B/D"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@react-navigation/core",
3
3
  "description": "Core utilities for building navigators",
4
- "version": "8.0.0-alpha.34",
4
+ "version": "8.0.0-alpha.35",
5
5
  "keywords": [
6
6
  "react",
7
7
  "react-native",
@@ -89,5 +89,5 @@
89
89
  ]
90
90
  ]
91
91
  },
92
- "gitHead": "73f8c2982a8999f1e1dfb1cfbeae9d8dab0c1cc2"
92
+ "gitHead": "400f7128965fc92c335a3f6c27e3fa26113915e4"
93
93
  }
@@ -403,10 +403,17 @@ export function BaseNavigationContainer<ParamList extends {} = RootParamList>({
403
403
  }
404
404
  );
405
405
 
406
+ const isOptionsListenerReadyRef = React.useRef(false);
406
407
  const lastEmittedOptionsRef = React.useRef<object | undefined>(undefined);
407
408
 
408
- const onOptionsChange = useLatestCallback((options: object) => {
409
- if (lastEmittedOptionsRef.current === options) {
409
+ const onOptionsChange = useLatestCallback(() => {
410
+ if (!isOptionsListenerReadyRef.current) {
411
+ return;
412
+ }
413
+
414
+ const options = getCurrentOptions();
415
+
416
+ if (options == null || lastEmittedOptionsRef.current === options) {
410
417
  return;
411
418
  }
412
419
 
@@ -418,6 +425,16 @@ export function BaseNavigationContainer<ParamList extends {} = RootParamList>({
418
425
  });
419
426
  });
420
427
 
428
+ React.useEffect(() => {
429
+ isOptionsListenerReadyRef.current = true;
430
+
431
+ onOptionsChange();
432
+
433
+ return () => {
434
+ isOptionsListenerReadyRef.current = false;
435
+ };
436
+ }, [onOptionsChange]);
437
+
421
438
  const lastEmittedStateRef = React.useRef<State>(undefined);
422
439
 
423
440
  const getIsStateEmitted = useLatestCallback(
@@ -74,7 +74,7 @@ type NavigationBuilderContextValue = {
74
74
  target: string | undefined;
75
75
  data: unknown;
76
76
  }) => void;
77
- onOptionsChange: (options: object) => void;
77
+ onOptionsChange: () => void;
78
78
  getIsStateEmitted: () => boolean;
79
79
  scheduleUpdate: (callback: () => void) => void;
80
80
  flushUpdates: () => void;
@@ -15,7 +15,11 @@ import {
15
15
  import { NavigationRootContext } from './NavigationRootContext';
16
16
  import { PreventRemoveContext } from './PreventRemoveContext';
17
17
  import { StaticTreeContext } from './StaticTreeContext';
18
- import { FocusedRouteKeyContext, IsFocusedContext } from './useIsFocused';
18
+ import {
19
+ FocusedRouteKeyContext,
20
+ IsFocusedContext,
21
+ IsFocusedGetterContext,
22
+ } from './useIsFocused';
19
23
  import {
20
24
  NamedNavigationStateListenerListContext,
21
25
  NavigationStateListenerContext,
@@ -50,13 +54,17 @@ export function NavigationIndependentTree({
50
54
  <NavigationRouteContext.Provider value={undefined}>
51
55
  <NavigationContext.Provider value={undefined}>
52
56
  <IsFocusedContext.Provider value={undefined}>
53
- <IsScreenContext.Provider value={false}>
54
- <NavigationFocusedRouteStateContext.Provider
55
- value={undefined}
56
- >
57
- {children}
58
- </NavigationFocusedRouteStateContext.Provider>
59
- </IsScreenContext.Provider>
57
+ <IsFocusedGetterContext.Provider
58
+ value={undefined}
59
+ >
60
+ <IsScreenContext.Provider value={false}>
61
+ <NavigationFocusedRouteStateContext.Provider
62
+ value={undefined}
63
+ >
64
+ {children}
65
+ </NavigationFocusedRouteStateContext.Provider>
66
+ </IsScreenContext.Provider>
67
+ </IsFocusedGetterContext.Provider>
60
68
  </IsFocusedContext.Provider>
61
69
  </NavigationContext.Provider>
62
70
  </NavigationRouteContext.Provider>
@@ -1,8 +1,13 @@
1
1
  import type { ParamListBase, Route } from '@react-navigation/routers';
2
2
  import * as React from 'react';
3
+ import useLatestCallback from 'use-latest-callback';
3
4
 
4
5
  import type { NavigationProp } from './types';
5
- import { FocusedRouteKeyContext, IsFocusedContext } from './useIsFocused';
6
+ import {
7
+ FocusedRouteKeyContext,
8
+ IsFocusedContext,
9
+ IsFocusedGetterContext,
10
+ } from './useIsFocused';
6
11
  import { useLazyValue } from './useLazyValue';
7
12
  import { NamedNavigationStateListenerProvider } from './useNavigationState';
8
13
 
@@ -62,6 +67,8 @@ export function NavigationProvider({ route, navigation, children }: Props) {
62
67
  ? focusedRouteKey === route.key
63
68
  : false;
64
69
 
70
+ const getIsFocused = useLatestCallback(() => isFocused);
71
+
65
72
  return (
66
73
  <NamedNavigationStateListenerProvider name={route.name}>
67
74
  <NamedRouteContextListContext.Provider value={NamedRouteContextList}>
@@ -69,9 +76,11 @@ export function NavigationProvider({ route, navigation, children }: Props) {
69
76
  <NavigationRouteContext.Provider value={route}>
70
77
  <NavigationContext.Provider value={navigation}>
71
78
  <IsFocusedContext.Provider value={isFocused}>
72
- <IsScreenContext.Provider value={true}>
73
- {children}
74
- </IsScreenContext.Provider>
79
+ <IsFocusedGetterContext.Provider value={getIsFocused}>
80
+ <IsScreenContext.Provider value={true}>
81
+ {children}
82
+ </IsScreenContext.Provider>
83
+ </IsFocusedGetterContext.Provider>
75
84
  </IsFocusedContext.Provider>
76
85
  </NavigationContext.Provider>
77
86
  </NavigationRouteContext.Provider>
@@ -50,15 +50,9 @@ export function PreventRemoveProvider({ children }: Props) {
50
50
 
51
51
  const setPreventRemove = useLatestCallback(
52
52
  (id: string, routeKey: string, preventRemove: boolean): void => {
53
- if (
54
- preventRemove &&
55
- (navigation == null ||
56
- navigation
57
- ?.getState()
58
- .routes.every((route) => route.key !== routeKey))
59
- ) {
53
+ if (preventRemove && navigation == null) {
60
54
  throw new Error(
61
- `Couldn't find a route with the key ${routeKey}. Is your component inside NavigationContent?`
55
+ "Couldn't find a navigation object. This is likely a bug in the navigator.\n\nIf you're using a custom navigator, make sure that the navigator content is wrapped by the 'render' function returned by 'useNavigationBuilder'."
62
56
  );
63
57
  }
64
58
 
package/src/SceneView.tsx CHANGED
@@ -22,6 +22,7 @@ type Props<State extends NavigationState, ScreenOptions extends {}> = {
22
22
  navigation: NavigationProp<ParamListBase, string, State, ScreenOptions>;
23
23
  route: Route<string>;
24
24
  routeState: NavigationState | PartialState<NavigationState> | undefined;
25
+ getFocusedRouteKey: () => string | undefined;
25
26
  getState: () => State;
26
27
  setState: (state: State) => void;
27
28
  subscribe: (callback: () => void) => () => void;
@@ -41,6 +42,7 @@ export function SceneView<
41
42
  route,
42
43
  navigation,
43
44
  routeState,
45
+ getFocusedRouteKey,
44
46
  getState,
45
47
  setState,
46
48
  subscribe,
@@ -53,7 +55,7 @@ export function SceneView<
53
55
  const { addOptionsGetter } = useOptionsGetters({
54
56
  key: route.key,
55
57
  options,
56
- navigation,
58
+ getFocusedRouteKey,
57
59
  });
58
60
 
59
61
  const setKey = React.useCallback((key: string) => {
@@ -6,6 +6,7 @@ import type {
6
6
  Router,
7
7
  } from '@react-navigation/routers';
8
8
  import * as React from 'react';
9
+ import useLatestCallback from 'use-latest-callback';
9
10
 
10
11
  import {
11
12
  type AddKeyedListener,
@@ -68,11 +69,11 @@ type Options<
68
69
  ScreenOptions extends {},
69
70
  EventMap extends EventMapBase,
70
71
  > = {
71
- routes: State['routes'];
72
72
  screens: Record<
73
73
  string,
74
74
  ScreenConfigWithParent<State, ScreenOptions, EventMap>
75
75
  >;
76
+ state: State;
76
77
  navigation: NavigationHelpers<ParamListBase>;
77
78
  screenOptions: ScreenOptionsOrCallback<ScreenOptions> | undefined;
78
79
  screenLayout: ScreenLayout<ScreenOptions> | undefined;
@@ -101,8 +102,8 @@ export function useDescriptors<
101
102
  ScreenOptions extends {},
102
103
  EventMap extends EventMapBase,
103
104
  >({
104
- routes,
105
105
  screens,
106
+ state,
106
107
  navigation,
107
108
  screenOptions,
108
109
  screenLayout,
@@ -117,6 +118,7 @@ export function useDescriptors<
117
118
  emitter,
118
119
  }: Options<State, ScreenOptions, EventMap>) {
119
120
  const theme = React.use(ThemeContext);
121
+
120
122
  const [options, setOptions] = React.useState<
121
123
  Record<string, Partial<ScreenOptions> | undefined>
122
124
  >({});
@@ -167,7 +169,7 @@ export function useDescriptors<
167
169
  EventMap,
168
170
  ActionHelpers
169
171
  >({
170
- routes,
172
+ routes: state.routes,
171
173
  getState,
172
174
  navigation,
173
175
  setOptions,
@@ -175,7 +177,11 @@ export function useDescriptors<
175
177
  emitter,
176
178
  });
177
179
 
178
- const cachedRoutes = useRouteCache(routes);
180
+ const cachedRoutes = useRouteCache(state.routes);
181
+
182
+ const getFocusedRouteKey = useLatestCallback(
183
+ () => state.routes[state.index]?.key
184
+ );
179
185
 
180
186
  const getOptions = (
181
187
  route: RouteProp<ParamListBase, string>,
@@ -258,6 +264,7 @@ export function useDescriptors<
258
264
  route={route}
259
265
  screen={screen}
260
266
  routeState={routeState}
267
+ getFocusedRouteKey={getFocusedRouteKey}
261
268
  getState={getState}
262
269
  setState={setState}
263
270
  subscribe={subscribe}
@@ -313,7 +320,13 @@ export function useDescriptors<
313
320
  }
314
321
 
315
322
  const customOptions = getOptions(route, navigation, overrides);
316
- const element = render(route, navigation, customOptions, routes[i]?.state);
323
+
324
+ const element = render(
325
+ route,
326
+ navigation,
327
+ customOptions,
328
+ state.routes[i]?.state
329
+ );
317
330
 
318
331
  acc[route.key] = {
319
332
  route,
@@ -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
  }
@@ -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.
@@ -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';
@@ -462,16 +461,20 @@ export function useNavigationBuilder<
462
461
 
463
462
  const { onEmitEvent, getIsStateEmitted } = useNavigationBuilderContext();
464
463
 
465
- const stateCleanupRef = React.useRef<boolean>(false);
466
- const lastStateRef = React.useRef<State | PartialState<State> | undefined>(
464
+ const mountStateRef = React.useRef<'initial' | 'mounted' | 'unmounted'>(
465
+ 'initial'
466
+ );
467
+
468
+ const previousStateRef = React.useRef(currentState);
469
+ const savedStateRef = React.useRef<State | PartialState<State> | undefined>(
467
470
  undefined
468
471
  );
469
472
 
470
473
  const setState = useLatestCallback(
471
474
  (state: State | PartialState<State> | undefined) => {
472
- if (stateCleanupRef.current) {
475
+ if (mountStateRef.current === 'unmounted') {
473
476
  // Store the state locally in case the current navigator is in `Activity`
474
- lastStateRef.current = state;
477
+ savedStateRef.current = state;
475
478
 
476
479
  // State might have been already cleaned up due to unmount
477
480
  // We don't want to update `route.state` in parent
@@ -483,6 +486,19 @@ export function useNavigationBuilder<
483
486
  }
484
487
  );
485
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
+
486
502
  const [
487
503
  stateBeforeInitialization,
488
504
  initializedState,
@@ -494,17 +510,10 @@ export function useNavigationBuilder<
494
510
  boolean,
495
511
  object | undefined,
496
512
  ] => {
497
- // If the state was already cleaned up, but we have it stored in ref,
498
- // It likely got cleaned up due to `<Activity mode="hidden">`
499
- // We should reuse this state to avoid remounting screens
500
- if (
501
- stateCleanupRef.current &&
502
- lastStateRef.current &&
503
- isStateValid(lastStateRef.current)
504
- ) {
505
- const state: State = isStateInitialized(lastStateRef.current)
506
- ? lastStateRef.current
507
- : router.getRehydratedState(lastStateRef.current, {
513
+ if (lastState && isStateValid(lastState)) {
514
+ const state: State = isStateInitialized(lastState)
515
+ ? lastState
516
+ : router.getRehydratedState(lastState, {
508
517
  routeNames,
509
518
  routeParamList,
510
519
  routeGetIdList,
@@ -605,7 +614,7 @@ export function useNavigationBuilder<
605
614
  // that some changes to routeConfigs are explicitly ignored, such as changes
606
615
  // to initialParams
607
616
  // eslint-disable-next-line react-hooks/exhaustive-deps
608
- }, [currentState, router, isStateValid]);
617
+ }, [currentState, lastState, router, isStateValid]);
609
618
 
610
619
  const previousRouteKeyListRef = React.useRef(routeKeyList);
611
620
 
@@ -645,7 +654,7 @@ export function useNavigationBuilder<
645
654
  // If the state isn't initialized, or stale, use the state we initialized instead
646
655
  // The state won't update until there's a change needed in the state we have initialized locally
647
656
  // So it'll be `undefined` or stale until the first navigation event happens
648
- isStateInitialized(currentState)
657
+ lastState === undefined && isStateInitialized(currentState)
649
658
  ? (currentState as State)
650
659
  : (initializedState as State);
651
660
 
@@ -770,7 +779,7 @@ export function useNavigationBuilder<
770
779
  : nextState;
771
780
  }
772
781
 
773
- React.useEffect(() => {
782
+ React.useInsertionEffect(() => {
774
783
  if (
775
784
  consumedParams &&
776
785
  didConsumeNestedParams &&
@@ -799,24 +808,29 @@ export function useNavigationBuilder<
799
808
  // So we override the state object we return to use the latest state as soon as possible
800
809
  state = nextState;
801
810
 
802
- // Last state to reuse if component gets cleaned up due to `<Activity mode="hidden">`
803
- React.useEffect(() => {
804
- lastStateRef.current = state;
811
+ React.useInsertionEffect(() => {
812
+ savedStateRef.current = state;
813
+ previousStateRef.current = currentState;
805
814
  });
806
815
 
807
- 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
+ }, []);
808
823
 
809
824
  React.useEffect(() => {
825
+ const savedState = savedStateRef.current ?? state;
826
+
810
827
  // In strict mode, React will double-invoke effects.
811
- // So we need to reset the flag if component was not unmounted
812
- stateCleanupRef.current = false;
828
+ // So we need to reset the status if component was not unmounted
829
+ mountStateRef.current = 'mounted';
813
830
 
814
831
  setKey(navigatorKey);
815
832
 
816
- if (
817
- (!getIsInitial() || getIsStateEmitted()) &&
818
- lastNotifiedStateRef.current !== state
819
- ) {
833
+ if (!getIsInitial() || getIsStateEmitted()) {
820
834
  // We need to notify the state update in these scenarios:
821
835
  // 1. If it's not the initial render of the component containing the navigator
822
836
  // 2. If the container has already emitted state before the navigator mounted
@@ -829,31 +843,25 @@ export function useNavigationBuilder<
829
843
  // So only checking for the initial render is not enough.
830
844
  // We also need to propagate the navigator's initial state when it mounts later,
831
845
  // so the container is notified that the nested state is now available.
832
- //
833
- // We only notify if the state is different from what we already notified
834
- // Otherwise, this goes into a loop when inside `<Activity mode="hidden">`
835
- setState(state);
836
-
837
- lastNotifiedStateRef.current = state;
846
+ setState(savedState);
838
847
  }
839
848
 
840
849
  return () => {
841
850
  // We need to clean up state for this navigator on unmount
842
851
  if (getCurrentState() !== undefined && getKey() === navigatorKey) {
843
852
  setCurrentState(undefined);
844
- stateCleanupRef.current = true;
845
853
  }
846
854
 
847
- // Reset so that StrictMode's second mount re-propagates state correctly.
848
- // Without this, the guard above sees the same reference and skips setState,
849
- // causing the initial state to be lost after cleanup wipes the container state.
850
- lastNotifiedStateRef.current = null;
855
+ mountStateRef.current = 'unmounted';
851
856
  };
852
857
  // eslint-disable-next-line react-hooks/exhaustive-deps
853
858
  }, []);
854
859
 
855
860
  const getState = useLatestCallback((): State => {
856
- const currentState = getCurrentState();
861
+ const currentState =
862
+ mountStateRef.current === 'unmounted'
863
+ ? savedStateRef.current
864
+ : getCurrentState();
857
865
 
858
866
  return deepFreeze(
859
867
  (isStateInitialized(currentState)
@@ -1026,8 +1034,8 @@ export function useNavigationBuilder<
1026
1034
  ScreenOptions,
1027
1035
  EventMap
1028
1036
  >({
1029
- routes: state.routes,
1030
1037
  screens,
1038
+ state,
1031
1039
  navigation,
1032
1040
  screenOptions,
1033
1041
  screenLayout,
@@ -1043,7 +1051,7 @@ export function useNavigationBuilder<
1043
1051
  emitter,
1044
1052
  });
1045
1053
 
1046
- const NavigationContent = useComponent((children: React.ReactNode) => {
1054
+ const render = (children: React.ReactNode) => {
1047
1055
  const focusedRoute = state.routes[state.index];
1048
1056
 
1049
1057
  if (focusedRoute == null) {
@@ -1080,12 +1088,12 @@ export function useNavigationBuilder<
1080
1088
  </NavigationHelpersContext.Provider>
1081
1089
  </NavigationMetaContext.Provider>
1082
1090
  );
1083
- });
1091
+ };
1084
1092
 
1085
1093
  return {
1086
1094
  state,
1087
1095
  navigation,
1088
1096
  descriptors,
1089
- NavigationContent,
1097
+ render,
1090
1098
  };
1091
1099
  }