@react-navigation/core 7.0.2 → 7.0.4

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 (62) hide show
  1. package/lib/commonjs/BaseNavigationContainer.js +10 -2
  2. package/lib/commonjs/BaseNavigationContainer.js.map +1 -1
  3. package/lib/commonjs/NavigationBuilderContext.js +7 -1
  4. package/lib/commonjs/NavigationBuilderContext.js.map +1 -1
  5. package/lib/commonjs/SceneView.js +29 -4
  6. package/lib/commonjs/SceneView.js.map +1 -1
  7. package/lib/commonjs/StaticNavigation.js +1 -1
  8. package/lib/commonjs/StaticNavigation.js.map +1 -1
  9. package/lib/commonjs/useDescriptors.js +5 -1
  10. package/lib/commonjs/useDescriptors.js.map +1 -1
  11. package/lib/commonjs/useNavigationBuilder.js +3 -2
  12. package/lib/commonjs/useNavigationBuilder.js.map +1 -1
  13. package/lib/commonjs/useScheduleUpdate.js +29 -0
  14. package/lib/commonjs/useScheduleUpdate.js.map +1 -0
  15. package/lib/commonjs/useSyncState.js +41 -2
  16. package/lib/commonjs/useSyncState.js.map +1 -1
  17. package/lib/module/BaseNavigationContainer.js +10 -2
  18. package/lib/module/BaseNavigationContainer.js.map +1 -1
  19. package/lib/module/NavigationBuilderContext.js +7 -1
  20. package/lib/module/NavigationBuilderContext.js.map +1 -1
  21. package/lib/module/SceneView.js +29 -4
  22. package/lib/module/SceneView.js.map +1 -1
  23. package/lib/module/StaticNavigation.js +1 -1
  24. package/lib/module/StaticNavigation.js.map +1 -1
  25. package/lib/module/useDescriptors.js +5 -1
  26. package/lib/module/useDescriptors.js.map +1 -1
  27. package/lib/module/useNavigationBuilder.js +3 -2
  28. package/lib/module/useNavigationBuilder.js.map +1 -1
  29. package/lib/module/useScheduleUpdate.js +23 -0
  30. package/lib/module/useScheduleUpdate.js.map +1 -0
  31. package/lib/module/useSyncState.js +40 -2
  32. package/lib/module/useSyncState.js.map +1 -1
  33. package/lib/typescript/commonjs/src/BaseNavigationContainer.d.ts.map +1 -1
  34. package/lib/typescript/commonjs/src/NavigationBuilderContext.d.ts +2 -0
  35. package/lib/typescript/commonjs/src/NavigationBuilderContext.d.ts.map +1 -1
  36. package/lib/typescript/commonjs/src/SceneView.d.ts.map +1 -1
  37. package/lib/typescript/commonjs/src/useDescriptors.d.ts.map +1 -1
  38. package/lib/typescript/commonjs/src/useNavigationBuilder.d.ts.map +1 -1
  39. package/lib/typescript/commonjs/src/useScheduleUpdate.d.ts +9 -0
  40. package/lib/typescript/commonjs/src/useScheduleUpdate.d.ts.map +1 -0
  41. package/lib/typescript/commonjs/src/useSyncState.d.ts +7 -1
  42. package/lib/typescript/commonjs/src/useSyncState.d.ts.map +1 -1
  43. package/lib/typescript/commonjs/tsconfig.build.tsbuildinfo +1 -1
  44. package/lib/typescript/module/src/BaseNavigationContainer.d.ts.map +1 -1
  45. package/lib/typescript/module/src/NavigationBuilderContext.d.ts +2 -0
  46. package/lib/typescript/module/src/NavigationBuilderContext.d.ts.map +1 -1
  47. package/lib/typescript/module/src/SceneView.d.ts.map +1 -1
  48. package/lib/typescript/module/src/useDescriptors.d.ts.map +1 -1
  49. package/lib/typescript/module/src/useNavigationBuilder.d.ts.map +1 -1
  50. package/lib/typescript/module/src/useScheduleUpdate.d.ts +9 -0
  51. package/lib/typescript/module/src/useScheduleUpdate.d.ts.map +1 -0
  52. package/lib/typescript/module/src/useSyncState.d.ts +7 -1
  53. package/lib/typescript/module/src/useSyncState.d.ts.map +1 -1
  54. package/lib/typescript/module/tsconfig.build.tsbuildinfo +1 -1
  55. package/package.json +6 -6
  56. package/src/BaseNavigationContainer.tsx +14 -4
  57. package/src/NavigationBuilderContext.tsx +8 -0
  58. package/src/SceneView.tsx +31 -3
  59. package/src/useDescriptors.tsx +11 -3
  60. package/src/useNavigationBuilder.tsx +2 -1
  61. package/src/useScheduleUpdate.tsx +21 -0
  62. package/src/useSyncState.tsx +48 -2
@@ -103,9 +103,10 @@ export const BaseNavigationContainer = React.forwardRef(
103
103
  );
104
104
  }
105
105
 
106
- const [state, getState, setState] = useSyncState<State>(() =>
107
- getPartialState(initialState == null ? undefined : initialState)
108
- );
106
+ const { state, getState, setState, scheduleUpdate, flushUpdates } =
107
+ useSyncState<State>(() =>
108
+ getPartialState(initialState == null ? undefined : initialState)
109
+ );
109
110
 
110
111
  const isFirstMountRef = React.useRef<boolean>(true);
111
112
 
@@ -260,9 +261,18 @@ export const BaseNavigationContainer = React.forwardRef(
260
261
  addKeyedListener,
261
262
  onDispatchAction,
262
263
  onOptionsChange,
264
+ scheduleUpdate,
265
+ flushUpdates,
263
266
  stackRef,
264
267
  }),
265
- [addListener, addKeyedListener, onDispatchAction, onOptionsChange]
268
+ [
269
+ addListener,
270
+ addKeyedListener,
271
+ onDispatchAction,
272
+ onOptionsChange,
273
+ scheduleUpdate,
274
+ flushUpdates,
275
+ ]
266
276
  );
267
277
 
268
278
  const isInitialRef = React.useRef(true);
@@ -61,8 +61,16 @@ export const NavigationBuilderContext = React.createContext<{
61
61
  onRouteFocus?: (key: string) => void;
62
62
  onDispatchAction: (action: NavigationAction, noop: boolean) => void;
63
63
  onOptionsChange: (options: object) => void;
64
+ scheduleUpdate: (callback: () => void) => void;
65
+ flushUpdates: () => void;
64
66
  stackRef?: React.MutableRefObject<string | undefined>;
65
67
  }>({
66
68
  onDispatchAction: () => undefined,
67
69
  onOptionsChange: () => undefined,
70
+ scheduleUpdate: () => {
71
+ throw new Error("Couldn't find a context for scheduling updates.");
72
+ },
73
+ flushUpdates: () => {
74
+ throw new Error("Couldn't find a context for flushing updates.");
75
+ },
68
76
  });
package/src/SceneView.tsx CHANGED
@@ -72,9 +72,37 @@ export function SceneView<
72
72
 
73
73
  setState({
74
74
  ...state,
75
- routes: state.routes.map((r) =>
76
- r.key === route.key ? { ...r, state: child } : r
77
- ),
75
+ routes: state.routes.map((r) => {
76
+ if (r.key !== route.key) {
77
+ return r;
78
+ }
79
+
80
+ const nextRoute = { ...r, state: child };
81
+
82
+ // Before updating the state, cleanup any nested screen and state
83
+ // This will avoid the navigator trying to handle them again
84
+ if (
85
+ nextRoute.params &&
86
+ (('state' in nextRoute.params &&
87
+ typeof nextRoute.params.state === 'object' &&
88
+ nextRoute.params.state !== null) ||
89
+ ('screen' in nextRoute.params &&
90
+ typeof nextRoute.params.screen === 'string'))
91
+ ) {
92
+ // @ts-expect-error: we don't have correct type for params
93
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
94
+ const { state, screen, params, initial, ...rest } =
95
+ nextRoute.params;
96
+
97
+ if (Object.keys(rest).length) {
98
+ nextRoute.params = rest;
99
+ } else {
100
+ delete nextRoute.params;
101
+ }
102
+ }
103
+
104
+ return nextRoute;
105
+ }),
78
106
  });
79
107
  },
80
108
  [getState, route.key, setState]
@@ -116,9 +116,13 @@ export function useDescriptors<
116
116
  const [options, setOptions] = React.useState<Record<string, ScreenOptions>>(
117
117
  {}
118
118
  );
119
- const { onDispatchAction, onOptionsChange, stackRef } = React.useContext(
120
- NavigationBuilderContext
121
- );
119
+ const {
120
+ onDispatchAction,
121
+ onOptionsChange,
122
+ scheduleUpdate,
123
+ flushUpdates,
124
+ stackRef,
125
+ } = React.useContext(NavigationBuilderContext);
122
126
 
123
127
  const context = React.useMemo(
124
128
  () => ({
@@ -129,6 +133,8 @@ export function useDescriptors<
129
133
  onRouteFocus,
130
134
  onDispatchAction,
131
135
  onOptionsChange,
136
+ scheduleUpdate,
137
+ flushUpdates,
132
138
  stackRef,
133
139
  }),
134
140
  [
@@ -139,6 +145,8 @@ export function useDescriptors<
139
145
  onRouteFocus,
140
146
  onDispatchAction,
141
147
  onOptionsChange,
148
+ scheduleUpdate,
149
+ flushUpdates,
142
150
  stackRef,
143
151
  ]
144
152
  );
@@ -46,6 +46,7 @@ import { useOnAction } from './useOnAction';
46
46
  import { useOnGetState } from './useOnGetState';
47
47
  import { useOnRouteFocus } from './useOnRouteFocus';
48
48
  import { useRegisterNavigator } from './useRegisterNavigator';
49
+ import { useScheduleUpdate } from './useScheduleUpdate';
49
50
 
50
51
  // This is to make TypeScript compiler happy
51
52
  PrivateValueStore;
@@ -561,7 +562,7 @@ export function useNavigationBuilder<
561
562
 
562
563
  const shouldUpdate = state !== nextState;
563
564
 
564
- useIsomorphicLayoutEffect(() => {
565
+ useScheduleUpdate(() => {
565
566
  if (shouldUpdate) {
566
567
  // If the state needs to be updated, we'll schedule an update
567
568
  setState(nextState);
@@ -0,0 +1,21 @@
1
+ import * as React from 'react';
2
+
3
+ import { NavigationBuilderContext } from './NavigationBuilderContext';
4
+ /**
5
+ * When screen config changes, we want to update the navigator in the same update phase.
6
+ * However, navigation state is in the root component and React won't let us update it from a child.
7
+ * This is a workaround for that, the scheduled update is stored in the ref without actually calling setState.
8
+ * It lets all subsequent updates access the latest state so it stays correct.
9
+ * Then we call setState during after the component updates.
10
+ */
11
+ export function useScheduleUpdate(callback: () => void) {
12
+ const { scheduleUpdate, flushUpdates } = React.useContext(
13
+ NavigationBuilderContext
14
+ );
15
+
16
+ // FIXME: This is potentially unsafe
17
+ // However, since we are using sync store, it might be fine
18
+ scheduleUpdate(callback);
19
+
20
+ React.useEffect(flushUpdates);
21
+ }
@@ -1,4 +1,5 @@
1
1
  import * as React from 'react';
2
+ import useLatestCallback from 'use-latest-callback';
2
3
 
3
4
  import { deepFreeze } from './deepFreeze';
4
5
 
@@ -19,10 +20,16 @@ const createStore = <T,>(getInitialState: () => T) => {
19
20
  return state;
20
21
  };
21
22
 
23
+ let isBatching = false;
24
+ let didUpdate = false;
25
+
22
26
  const setState = (newState: T) => {
23
27
  state = deepFreeze(newState);
28
+ didUpdate = true;
24
29
 
25
- listeners.forEach((listener) => listener());
30
+ if (!isBatching) {
31
+ listeners.forEach((listener) => listener());
32
+ }
26
33
  };
27
34
 
28
35
  const subscribe = (callback: () => void) => {
@@ -37,9 +44,21 @@ const createStore = <T,>(getInitialState: () => T) => {
37
44
  };
38
45
  };
39
46
 
47
+ const batchUpdates = (callback: () => void) => {
48
+ isBatching = true;
49
+ callback();
50
+ isBatching = false;
51
+
52
+ if (didUpdate) {
53
+ didUpdate = false;
54
+ listeners.forEach((listener) => listener());
55
+ }
56
+ };
57
+
40
58
  return {
41
59
  getState,
42
60
  setState,
61
+ batchUpdates,
43
62
  subscribe,
44
63
  };
45
64
  };
@@ -55,5 +74,32 @@ export function useSyncState<T>(getInitialState: () => T) {
55
74
 
56
75
  React.useDebugValue(state);
57
76
 
58
- return [state, store.getState, store.setState] as const;
77
+ const pendingUpdatesRef = React.useRef<(() => void)[]>([]);
78
+
79
+ const scheduleUpdate = useLatestCallback((callback: () => void) => {
80
+ pendingUpdatesRef.current.push(callback);
81
+ });
82
+
83
+ const flushUpdates = useLatestCallback(() => {
84
+ const pendingUpdates = pendingUpdatesRef.current;
85
+
86
+ pendingUpdatesRef.current = [];
87
+
88
+ if (pendingUpdates.length !== 0) {
89
+ store.batchUpdates(() => {
90
+ // Flush all the pending updates
91
+ for (const update of pendingUpdates) {
92
+ update();
93
+ }
94
+ });
95
+ }
96
+ });
97
+
98
+ return {
99
+ state,
100
+ getState: store.getState,
101
+ setState: store.setState,
102
+ scheduleUpdate,
103
+ flushUpdates,
104
+ } as const;
59
105
  }