@react-navigation/core 8.0.0-alpha.3 → 8.0.0-alpha.31

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 (285) hide show
  1. package/lib/module/BaseNavigationContainer.js +38 -19
  2. package/lib/module/BaseNavigationContainer.js.map +1 -1
  3. package/lib/module/ConsumedParamsContext.js +5 -0
  4. package/lib/module/ConsumedParamsContext.js.map +1 -0
  5. package/lib/module/DataLoading.js +127 -0
  6. package/lib/module/DataLoading.js.map +1 -0
  7. package/lib/module/NavigationBuilderContext.js +1 -0
  8. package/lib/module/NavigationBuilderContext.js.map +1 -1
  9. package/lib/module/NavigationIndependentTree.js +20 -8
  10. package/lib/module/NavigationIndependentTree.js.map +1 -1
  11. package/lib/module/NavigationProvider.js +22 -11
  12. package/lib/module/NavigationProvider.js.map +1 -1
  13. package/lib/module/NavigationStateContext.js +3 -0
  14. package/lib/module/NavigationStateContext.js.map +1 -1
  15. package/lib/module/PreventRemoveContext.js.map +1 -1
  16. package/lib/module/PreventRemoveProvider.js +37 -27
  17. package/lib/module/PreventRemoveProvider.js.map +1 -1
  18. package/lib/module/SceneView.js +10 -41
  19. package/lib/module/SceneView.js.map +1 -1
  20. package/lib/module/StaticNavigation.js +179 -50
  21. package/lib/module/StaticNavigation.js.map +1 -1
  22. package/lib/module/StaticTreeContext.js +5 -0
  23. package/lib/module/StaticTreeContext.js.map +1 -0
  24. package/lib/module/checkSerializable.js +18 -13
  25. package/lib/module/checkSerializable.js.map +1 -1
  26. package/lib/module/createNavigationContainerRef.js +6 -8
  27. package/lib/module/createNavigationContainerRef.js.map +1 -1
  28. package/lib/module/createNavigatorFactory.js +27 -5
  29. package/lib/module/createNavigatorFactory.js.map +1 -1
  30. package/lib/module/findFocusedRoute.js +7 -4
  31. package/lib/module/findFocusedRoute.js.map +1 -1
  32. package/lib/module/getActionFromState.js +45 -20
  33. package/lib/module/getActionFromState.js.map +1 -1
  34. package/lib/module/getPathFromState.js +171 -90
  35. package/lib/module/getPathFromState.js.map +1 -1
  36. package/lib/module/getPatternParts.js +20 -4
  37. package/lib/module/getPatternParts.js.map +1 -1
  38. package/lib/module/getStateFromPath.js +487 -217
  39. package/lib/module/getStateFromPath.js.map +1 -1
  40. package/lib/module/getStateFromRouteParams.js +23 -0
  41. package/lib/module/getStateFromRouteParams.js.map +1 -0
  42. package/lib/module/index.js +5 -4
  43. package/lib/module/index.js.map +1 -1
  44. package/lib/module/theming/useTheme.js +1 -1
  45. package/lib/module/theming/useTheme.js.map +1 -1
  46. package/lib/module/types.js +35 -0
  47. package/lib/module/types.js.map +1 -1
  48. package/lib/module/useDescriptors.js +21 -11
  49. package/lib/module/useDescriptors.js.map +1 -1
  50. package/lib/module/useEventEmitter.js +48 -33
  51. package/lib/module/useEventEmitter.js.map +1 -1
  52. package/lib/module/useFocusEvents.js +12 -4
  53. package/lib/module/useFocusEvents.js.map +1 -1
  54. package/lib/module/useFocusedListenersChildrenAdapter.js +2 -2
  55. package/lib/module/useFocusedListenersChildrenAdapter.js.map +1 -1
  56. package/lib/module/useIsFocused.js +7 -12
  57. package/lib/module/useIsFocused.js.map +1 -1
  58. package/lib/module/useKeyedChildListeners.js +4 -2
  59. package/lib/module/useKeyedChildListeners.js.map +1 -1
  60. package/lib/module/useLazyValue.js.map +1 -1
  61. package/lib/module/useNavigation.js +2 -15
  62. package/lib/module/useNavigation.js.map +1 -1
  63. package/lib/module/useNavigationBuilder.js +204 -131
  64. package/lib/module/useNavigationBuilder.js.map +1 -1
  65. package/lib/module/useNavigationCache.js +22 -17
  66. package/lib/module/useNavigationCache.js.map +1 -1
  67. package/lib/module/useNavigationHelpers.js +28 -10
  68. package/lib/module/useNavigationHelpers.js.map +1 -1
  69. package/lib/module/useNavigationIndependentTree.js +1 -1
  70. package/lib/module/useNavigationIndependentTree.js.map +1 -1
  71. package/lib/module/useNavigationState.js +125 -45
  72. package/lib/module/useNavigationState.js.map +1 -1
  73. package/lib/module/useOnAction.js +37 -7
  74. package/lib/module/useOnAction.js.map +1 -1
  75. package/lib/module/useOnGetState.js +6 -3
  76. package/lib/module/useOnGetState.js.map +1 -1
  77. package/lib/module/useOnPreventRemove.js +39 -30
  78. package/lib/module/useOnPreventRemove.js.map +1 -1
  79. package/lib/module/useOnRouteFocus.js +1 -1
  80. package/lib/module/useOnRouteFocus.js.map +1 -1
  81. package/lib/module/useOptionsGetters.js +10 -5
  82. package/lib/module/useOptionsGetters.js.map +1 -1
  83. package/lib/module/usePreventRemove.js +10 -3
  84. package/lib/module/usePreventRemove.js.map +1 -1
  85. package/lib/module/usePreventRemoveContext.js +1 -1
  86. package/lib/module/usePreventRemoveContext.js.map +1 -1
  87. package/lib/module/useRegisterNavigator.js +2 -2
  88. package/lib/module/useRegisterNavigator.js.map +1 -1
  89. package/lib/module/useRoute.js +2 -0
  90. package/lib/module/useRoute.js.map +1 -1
  91. package/lib/module/useRouteCache.js +17 -11
  92. package/lib/module/useRouteCache.js.map +1 -1
  93. package/lib/module/useScheduleUpdate.js +9 -9
  94. package/lib/module/useScheduleUpdate.js.map +1 -1
  95. package/lib/module/useStateForPath.js +1 -1
  96. package/lib/module/useStateForPath.js.map +1 -1
  97. package/lib/module/useSyncState.js +32 -9
  98. package/lib/module/useSyncState.js.map +1 -1
  99. package/lib/module/validatePathConfig.js +50 -36
  100. package/lib/module/validatePathConfig.js.map +1 -1
  101. package/lib/typescript/src/BaseNavigationContainer.d.ts +6 -3
  102. package/lib/typescript/src/BaseNavigationContainer.d.ts.map +1 -1
  103. package/lib/typescript/src/ConsumedParamsContext.d.ts +3 -0
  104. package/lib/typescript/src/ConsumedParamsContext.d.ts.map +1 -0
  105. package/lib/typescript/src/DataLoading.d.ts +21 -0
  106. package/lib/typescript/src/DataLoading.d.ts.map +1 -0
  107. package/lib/typescript/src/EnsureSingleNavigator.d.ts +1 -1
  108. package/lib/typescript/src/EnsureSingleNavigator.d.ts.map +1 -1
  109. package/lib/typescript/src/Group.d.ts +1 -1
  110. package/lib/typescript/src/Group.d.ts.map +1 -1
  111. package/lib/typescript/src/NavigationBuilderContext.d.ts +9 -8
  112. package/lib/typescript/src/NavigationBuilderContext.d.ts.map +1 -1
  113. package/lib/typescript/src/NavigationContainerRefContext.d.ts +1 -1
  114. package/lib/typescript/src/NavigationContainerRefContext.d.ts.map +1 -1
  115. package/lib/typescript/src/NavigationFocusedRouteStateContext.d.ts +4 -4
  116. package/lib/typescript/src/NavigationFocusedRouteStateContext.d.ts.map +1 -1
  117. package/lib/typescript/src/NavigationHelpersContext.d.ts +1 -1
  118. package/lib/typescript/src/NavigationHelpersContext.d.ts.map +1 -1
  119. package/lib/typescript/src/NavigationIndependentTree.d.ts +1 -1
  120. package/lib/typescript/src/NavigationIndependentTree.d.ts.map +1 -1
  121. package/lib/typescript/src/NavigationProvider.d.ts +12 -13
  122. package/lib/typescript/src/NavigationProvider.d.ts.map +1 -1
  123. package/lib/typescript/src/NavigationStateContext.d.ts +4 -3
  124. package/lib/typescript/src/NavigationStateContext.d.ts.map +1 -1
  125. package/lib/typescript/src/PreventRemoveContext.d.ts +1 -0
  126. package/lib/typescript/src/PreventRemoveContext.d.ts.map +1 -1
  127. package/lib/typescript/src/PreventRemoveProvider.d.ts +1 -1
  128. package/lib/typescript/src/PreventRemoveProvider.d.ts.map +1 -1
  129. package/lib/typescript/src/SceneView.d.ts +4 -2
  130. package/lib/typescript/src/SceneView.d.ts.map +1 -1
  131. package/lib/typescript/src/Screen.d.ts +1 -1
  132. package/lib/typescript/src/Screen.d.ts.map +1 -1
  133. package/lib/typescript/src/StaticContainer.d.ts +1 -1
  134. package/lib/typescript/src/StaticContainer.d.ts.map +1 -1
  135. package/lib/typescript/src/StaticNavigation.d.ts +189 -109
  136. package/lib/typescript/src/StaticNavigation.d.ts.map +1 -1
  137. package/lib/typescript/src/StaticTreeContext.d.ts +4 -0
  138. package/lib/typescript/src/StaticTreeContext.d.ts.map +1 -0
  139. package/lib/typescript/src/checkSerializable.d.ts +8 -4
  140. package/lib/typescript/src/checkSerializable.d.ts.map +1 -1
  141. package/lib/typescript/src/createNavigationContainerRef.d.ts +1 -1
  142. package/lib/typescript/src/createNavigationContainerRef.d.ts.map +1 -1
  143. package/lib/typescript/src/createNavigatorFactory.d.ts +23 -5
  144. package/lib/typescript/src/createNavigatorFactory.d.ts.map +1 -1
  145. package/lib/typescript/src/findFocusedRoute.d.ts +3 -8
  146. package/lib/typescript/src/findFocusedRoute.d.ts.map +1 -1
  147. package/lib/typescript/src/getActionFromState.d.ts +4 -4
  148. package/lib/typescript/src/getActionFromState.d.ts.map +1 -1
  149. package/lib/typescript/src/getPathFromState.d.ts +3 -3
  150. package/lib/typescript/src/getPathFromState.d.ts.map +1 -1
  151. package/lib/typescript/src/getPatternParts.d.ts +1 -0
  152. package/lib/typescript/src/getPatternParts.d.ts.map +1 -1
  153. package/lib/typescript/src/getStateFromPath.d.ts +4 -7
  154. package/lib/typescript/src/getStateFromPath.d.ts.map +1 -1
  155. package/lib/typescript/src/getStateFromRouteParams.d.ts +3 -0
  156. package/lib/typescript/src/getStateFromRouteParams.d.ts.map +1 -0
  157. package/lib/typescript/src/index.d.ts +36 -33
  158. package/lib/typescript/src/index.d.ts.map +1 -1
  159. package/lib/typescript/src/theming/ThemeContext.d.ts +1 -1
  160. package/lib/typescript/src/theming/ThemeContext.d.ts.map +1 -1
  161. package/lib/typescript/src/theming/ThemeProvider.d.ts +2 -2
  162. package/lib/typescript/src/theming/ThemeProvider.d.ts.map +1 -1
  163. package/lib/typescript/src/theming/useTheme.d.ts +1 -1
  164. package/lib/typescript/src/theming/useTheme.d.ts.map +1 -1
  165. package/lib/typescript/src/types.d.ts +260 -159
  166. package/lib/typescript/src/types.d.ts.map +1 -1
  167. package/lib/typescript/src/useChildListeners.d.ts +3 -3
  168. package/lib/typescript/src/useChildListeners.d.ts.map +1 -1
  169. package/lib/typescript/src/useComponent.d.ts +1 -1
  170. package/lib/typescript/src/useComponent.d.ts.map +1 -1
  171. package/lib/typescript/src/useDescriptors.d.ts +10 -9
  172. package/lib/typescript/src/useDescriptors.d.ts.map +1 -1
  173. package/lib/typescript/src/useEventEmitter.d.ts +2 -2
  174. package/lib/typescript/src/useEventEmitter.d.ts.map +1 -1
  175. package/lib/typescript/src/useFocusEvents.d.ts +2 -2
  176. package/lib/typescript/src/useFocusEvents.d.ts.map +1 -1
  177. package/lib/typescript/src/useFocusedListenersChildrenAdapter.d.ts +2 -2
  178. package/lib/typescript/src/useFocusedListenersChildrenAdapter.d.ts.map +1 -1
  179. package/lib/typescript/src/useIsFocused.d.ts +3 -0
  180. package/lib/typescript/src/useIsFocused.d.ts.map +1 -1
  181. package/lib/typescript/src/useKeyedChildListeners.d.ts +3 -3
  182. package/lib/typescript/src/useKeyedChildListeners.d.ts.map +1 -1
  183. package/lib/typescript/src/useLazyValue.d.ts.map +1 -1
  184. package/lib/typescript/src/useNavigation.d.ts +4 -2
  185. package/lib/typescript/src/useNavigation.d.ts.map +1 -1
  186. package/lib/typescript/src/useNavigationBuilder.d.ts +23 -30
  187. package/lib/typescript/src/useNavigationBuilder.d.ts.map +1 -1
  188. package/lib/typescript/src/useNavigationCache.d.ts +3 -3
  189. package/lib/typescript/src/useNavigationCache.d.ts.map +1 -1
  190. package/lib/typescript/src/useNavigationContainerRef.d.ts +1 -1
  191. package/lib/typescript/src/useNavigationContainerRef.d.ts.map +1 -1
  192. package/lib/typescript/src/useNavigationHelpers.d.ts +19 -27
  193. package/lib/typescript/src/useNavigationHelpers.d.ts.map +1 -1
  194. package/lib/typescript/src/useNavigationState.d.ts +17 -3
  195. package/lib/typescript/src/useNavigationState.d.ts.map +1 -1
  196. package/lib/typescript/src/useOnAction.d.ts +9 -9
  197. package/lib/typescript/src/useOnAction.d.ts.map +1 -1
  198. package/lib/typescript/src/useOnGetState.d.ts +1 -1
  199. package/lib/typescript/src/useOnGetState.d.ts.map +1 -1
  200. package/lib/typescript/src/useOnPreventRemove.d.ts +5 -9
  201. package/lib/typescript/src/useOnPreventRemove.d.ts.map +1 -1
  202. package/lib/typescript/src/useOnRouteFocus.d.ts +6 -6
  203. package/lib/typescript/src/useOnRouteFocus.d.ts.map +1 -1
  204. package/lib/typescript/src/useOptionsGetters.d.ts +1 -1
  205. package/lib/typescript/src/useOptionsGetters.d.ts.map +1 -1
  206. package/lib/typescript/src/usePreventRemove.d.ts.map +1 -1
  207. package/lib/typescript/src/usePreventRemoveContext.d.ts +2 -1
  208. package/lib/typescript/src/usePreventRemoveContext.d.ts.map +1 -1
  209. package/lib/typescript/src/useRoute.d.ts +7 -3
  210. package/lib/typescript/src/useRoute.d.ts.map +1 -1
  211. package/lib/typescript/src/useRouteCache.d.ts +5 -5
  212. package/lib/typescript/src/useRouteCache.d.ts.map +1 -1
  213. package/lib/typescript/src/useScheduleUpdate.d.ts +5 -4
  214. package/lib/typescript/src/useScheduleUpdate.d.ts.map +1 -1
  215. package/lib/typescript/src/useStateForPath.d.ts +1 -1
  216. package/lib/typescript/src/useStateForPath.d.ts.map +1 -1
  217. package/lib/typescript/src/useSyncState.d.ts +1 -0
  218. package/lib/typescript/src/useSyncState.d.ts.map +1 -1
  219. package/lib/typescript/src/utilities.d.ts +74 -37
  220. package/lib/typescript/src/utilities.d.ts.map +1 -1
  221. package/lib/typescript/src/validatePathConfig.d.ts.map +1 -1
  222. package/package.json +23 -22
  223. package/src/BaseNavigationContainer.tsx +362 -334
  224. package/src/ConsumedParamsContext.tsx +5 -0
  225. package/src/DataLoading.tsx +203 -0
  226. package/src/NavigationBuilderContext.tsx +14 -9
  227. package/src/NavigationFocusedRouteStateContext.tsx +4 -4
  228. package/src/NavigationIndependentTree.tsx +23 -12
  229. package/src/NavigationProvider.tsx +26 -15
  230. package/src/NavigationStateContext.tsx +9 -6
  231. package/src/PreventRemoveContext.tsx +1 -0
  232. package/src/PreventRemoveProvider.tsx +51 -46
  233. package/src/SceneView.tsx +11 -39
  234. package/src/StaticNavigation.tsx +659 -307
  235. package/src/StaticTreeContext.tsx +7 -0
  236. package/src/checkSerializable.tsx +35 -19
  237. package/src/createNavigationContainerRef.tsx +12 -9
  238. package/src/createNavigatorFactory.tsx +92 -8
  239. package/src/findFocusedRoute.tsx +16 -16
  240. package/src/getActionFromState.tsx +87 -51
  241. package/src/getPathFromState.tsx +254 -128
  242. package/src/getPatternParts.tsx +25 -4
  243. package/src/getStateFromPath.tsx +821 -331
  244. package/src/getStateFromRouteParams.tsx +59 -0
  245. package/src/index.tsx +55 -5
  246. package/src/theming/useTheme.tsx +1 -1
  247. package/src/types.tsx +798 -373
  248. package/src/useDescriptors.tsx +31 -14
  249. package/src/useEventEmitter.tsx +52 -38
  250. package/src/useFocusEvents.tsx +15 -7
  251. package/src/useFocusedListenersChildrenAdapter.tsx +2 -2
  252. package/src/useIsFocused.tsx +14 -21
  253. package/src/useKeyedChildListeners.tsx +4 -2
  254. package/src/useLazyValue.tsx +2 -2
  255. package/src/useNavigation.tsx +7 -24
  256. package/src/useNavigationBuilder.tsx +314 -186
  257. package/src/useNavigationCache.tsx +32 -25
  258. package/src/useNavigationHelpers.tsx +27 -10
  259. package/src/useNavigationIndependentTree.tsx +1 -1
  260. package/src/useNavigationState.tsx +168 -69
  261. package/src/useOnAction.tsx +61 -13
  262. package/src/useOnGetState.tsx +5 -3
  263. package/src/useOnPreventRemove.tsx +75 -42
  264. package/src/useOnRouteFocus.tsx +10 -12
  265. package/src/useOptionsGetters.tsx +11 -5
  266. package/src/usePreventRemove.tsx +12 -3
  267. package/src/usePreventRemoveContext.tsx +1 -1
  268. package/src/useRegisterNavigator.tsx +2 -2
  269. package/src/useRoute.tsx +20 -6
  270. package/src/useRouteCache.tsx +22 -16
  271. package/src/useScheduleUpdate.tsx +9 -10
  272. package/src/useStateForPath.tsx +1 -1
  273. package/src/useSyncState.tsx +35 -9
  274. package/src/utilities.tsx +144 -31
  275. package/src/validatePathConfig.tsx +86 -57
  276. package/lib/module/CurrentRenderContext.js +0 -10
  277. package/lib/module/CurrentRenderContext.js.map +0 -1
  278. package/lib/module/useCurrentRender.js +0 -19
  279. package/lib/module/useCurrentRender.js.map +0 -1
  280. package/lib/typescript/src/CurrentRenderContext.d.ts +0 -9
  281. package/lib/typescript/src/CurrentRenderContext.d.ts.map +0 -1
  282. package/lib/typescript/src/useCurrentRender.d.ts +0 -14
  283. package/lib/typescript/src/useCurrentRender.d.ts.map +0 -1
  284. package/src/CurrentRenderContext.tsx +0 -9
  285. package/src/useCurrentRender.tsx +0 -31
@@ -23,8 +23,8 @@ type Options<
23
23
  Partial<NavigationProp<ParamListBase, string, any, any, any>>;
24
24
  setOptions: (
25
25
  cb: (
26
- options: Record<string, ScreenOptions>
27
- ) => Record<string, ScreenOptions>
26
+ options: Record<string, Partial<ScreenOptions> | undefined>
27
+ ) => Record<string, Partial<ScreenOptions> | undefined>
28
28
  ) => void;
29
29
  router: Router<State, NavigationAction>;
30
30
  emitter: NavigationEventEmitter<EventMap>;
@@ -72,8 +72,8 @@ export function useNavigationCache<
72
72
  router,
73
73
  emitter,
74
74
  }: Options<State, ScreenOptions, EventMap>) {
75
- const parentNavigationHelpers = React.useContext(NavigationContext);
76
- const { stackRef } = React.useContext(NavigationBuilderContext);
75
+ const parentNavigationHelpers = React.use(NavigationContext);
76
+ const { stackRef } = React.use(NavigationBuilderContext);
77
77
 
78
78
  // Cache object which holds navigation objects for each screen
79
79
  // We use `React.useMemo` instead of `React.useRef` coz we want to invalidate it when deps change
@@ -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,
@@ -174,7 +177,7 @@ export function useNavigationCache<
174
177
 
175
178
  return parentNavigationHelpers;
176
179
  },
177
- setOptions: (options: object) => {
180
+ setOptions: (options: Partial<ScreenOptions>) => {
178
181
  setOptions((o) => ({
179
182
  ...o,
180
183
  [route.key]: { ...o[route.key], ...options },
@@ -183,7 +186,7 @@ export function useNavigationCache<
183
186
  isFocused: () => {
184
187
  const state = rest.getState();
185
188
 
186
- if (state.routes[state.index].key !== route.key) {
189
+ if (state.routes[state.index]?.key !== route.key) {
187
190
  return false;
188
191
  }
189
192
 
@@ -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
  }
@@ -19,9 +19,9 @@ type Options<State extends NavigationState, Action extends NavigationAction> = {
19
19
  onAction: (action: NavigationAction) => boolean;
20
20
  onUnhandledAction: (action: NavigationAction) => void;
21
21
  getState: () => State;
22
+ state: State;
22
23
  emitter: NavigationEventEmitter<any>;
23
24
  router: Router<State, Action>;
24
- stateRef: React.RefObject<State | null>;
25
25
  };
26
26
 
27
27
  /**
@@ -37,11 +37,28 @@ export function useNavigationHelpers<
37
37
  onAction,
38
38
  onUnhandledAction,
39
39
  getState,
40
+ state,
40
41
  emitter,
41
42
  router,
42
- stateRef,
43
43
  }: Options<State, Action>) {
44
- const parentNavigationHelpers = React.useContext(NavigationContext);
44
+ const parentNavigationHelpers = React.use(NavigationContext);
45
+
46
+ // In some cases (e.g. route names change), internal state might have changed
47
+ // But it hasn't been committed yet, so hasn't propagated to any listeners
48
+ // During this time, we need to return the internal state in `getState`
49
+ // Otherwise it can return an inconsistent state during render in children
50
+ // This may affect hooks like `useNavigationState` that need to read the state during render
51
+ // To avoid this, we use a ref for render phase, and immediately clear it on commit
52
+ // The ref won't be cleared if the render is discarded, e.g. when interrupted
53
+ // So we also track the stored state the render was based on
54
+ // Then ignore the ref once the stored state changes due to another dispatch
55
+ const stateRef = React.useRef<{ state: State; base: State } | null>(null);
56
+
57
+ stateRef.current = { state, base: getState() };
58
+
59
+ React.useInsertionEffect(() => {
60
+ stateRef.current = null;
61
+ });
45
62
 
46
63
  return React.useMemo(() => {
47
64
  const dispatch = (op: Action | ((state: State) => Action)) => {
@@ -87,16 +104,16 @@ export function useNavigationHelpers<
87
104
  );
88
105
  },
89
106
  getState: (): State => {
107
+ const current = getState();
108
+ const pending = stateRef.current;
109
+
90
110
  // FIXME: Workaround for when the state is read during render
91
- // By this time, we haven't committed the new state yet
92
- // Without this `useSyncExternalStore` will keep reading the old state
93
- // This may result in `useNavigationState` or `useIsFocused` returning wrong values
94
- // Apart from `useSyncExternalStore`, `getState` should never be called during render
95
- if (stateRef.current != null) {
96
- return stateRef.current;
111
+ // Apart from subscriptions, `getState` should never be called during render
112
+ if (pending != null && pending.base === current) {
113
+ return pending.state;
97
114
  }
98
115
 
99
- return getState();
116
+ return current;
100
117
  },
101
118
  } as NavigationHelpers<ParamListBase, EventMap> & ActionHelpers;
102
119
 
@@ -3,5 +3,5 @@ import * as React from 'react';
3
3
  import { NavigationIndependentTreeContext } from './NavigationIndependentTreeContext';
4
4
 
5
5
  export function useNavigationIndependentTree() {
6
- return React.useContext(NavigationIndependentTreeContext);
6
+ return React.use(NavigationIndependentTreeContext);
7
7
  }
@@ -1,18 +1,19 @@
1
1
  import type { NavigationState, ParamListBase } from '@react-navigation/routers';
2
2
  import * as React from 'react';
3
- import useLatestCallback from 'use-latest-callback';
4
- import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/with-selector';
5
3
 
6
- import type {
7
- NavigationListForNested,
8
- NavigationProp,
9
- RootNavigator,
10
- } from './types';
11
- import { useNavigation } from './useNavigation';
4
+ import type { NavigationListForNested, RootNavigator } from './types';
5
+
6
+ type NavigationStateEvent = 'update' | 'commit';
12
7
 
13
8
  type NavigationStateListener = {
14
9
  getState: () => NavigationState<ParamListBase>;
15
- subscribe: (callback: () => void) => () => void;
10
+ getSnapshot: () => NavigationState<ParamListBase>;
11
+ subscribe: (callback: (event: NavigationStateEvent) => void) => () => void;
12
+ };
13
+
14
+ type NavigationStateEntry = {
15
+ listener: NavigationStateListener;
16
+ state: NavigationState<ParamListBase>;
16
17
  };
17
18
 
18
19
  type NavigationStateForNested<
@@ -35,8 +36,8 @@ type NavigationStateForNested<
35
36
  export function useNavigationState<
36
37
  const T,
37
38
  const Navigator = RootNavigator,
38
- const RouteName extends
39
- keyof NavigationListForNested<Navigator> = keyof NavigationListForNested<Navigator>,
39
+ const RouteName extends keyof NavigationListForNested<Navigator> =
40
+ keyof NavigationListForNested<Navigator>,
40
41
  >(
41
42
  routeName: RouteName,
42
43
  selector: (state: NavigationStateForNested<Navigator, RouteName>) => T
@@ -45,99 +46,161 @@ export function useNavigationState<T>(
45
46
  selector: (state: NavigationState<ParamListBase>) => T
46
47
  ): T;
47
48
  export function useNavigationState(...args: unknown[]): unknown {
48
- let navigation: NavigationProp<ParamListBase> | undefined,
49
- stateListener: NavigationStateListener | undefined,
50
- selector;
49
+ const selector = typeof args[0] === 'string' ? args[1] : args[0];
51
50
 
52
- if (typeof args[0] === 'string') {
53
- // `useNavigation` uses `use` internally, so it's fine to call it conditionally
54
- // @ts-expect-error we can't specify the type here
55
- // eslint-disable-next-line react-hooks/rules-of-hooks
56
- navigation = useNavigation(args[0]);
57
- selector = args[1];
58
- } else {
59
- selector = args[0];
51
+ if (typeof selector !== 'function') {
52
+ throw new Error(
53
+ `A selector function must be provided (got ${typeof selector}).`
54
+ );
60
55
  }
61
56
 
62
- if (navigation == null) {
63
- stateListener = React.use(NavigationStateListenerContext);
57
+ // @ts-expect-error: the public overloads guarantee the selector's type, but
58
+ // the `unknown[]` implementation signature can't express it
59
+ const select: (state: NavigationState<ParamListBase>) => unknown = selector;
60
+
61
+ let listener: NavigationStateListener | undefined;
62
+
63
+ if (typeof args[0] === 'string') {
64
+ const name = args[0];
65
+ const listeners = React.use(NamedNavigationStateListenerListContext);
64
66
 
65
- if (stateListener == null) {
67
+ listener = listeners?.[name];
68
+
69
+ if (listener == null) {
70
+ throw new Error(
71
+ `Couldn't find a navigator for the route '${name}' in any of the parent screens. Is your component inside the correct screen?`
72
+ );
73
+ }
74
+ } else {
75
+ listener = React.use(NavigationStateListenerContext);
76
+
77
+ if (listener == null) {
66
78
  throw new Error(
67
79
  "Couldn't get the navigation state. Is your component inside a navigator?"
68
80
  );
69
81
  }
70
82
  }
71
83
 
72
- const subscribe = React.useCallback(
73
- (callback: () => void) => {
74
- if (navigation) {
75
- return navigation.addListener('state', callback);
76
- } else if (stateListener) {
77
- return stateListener.subscribe(callback);
78
- } else {
79
- throw new Error(
80
- "Couldn't subscribe to navigation state changes. This is not expected."
81
- );
84
+ const { getState, getSnapshot, subscribe } = listener;
85
+
86
+ // The store can contain state from a transition that hasn't committed yet.
87
+ // Reading it during render can show state that doesn't match the navigator in that render.
88
+ // The reducer lets React process state in the same render lane as the navigator.
89
+ const [entry, dispatch] = React.useReducer(
90
+ (previous: NavigationStateEntry, event: NavigationStateEvent) => {
91
+ // Navigation state can change between the dispatch and the reducer running.
92
+ // Reading here ensures the processed event uses the state for that render.
93
+ const state = event === 'update' ? getState() : getSnapshot();
94
+
95
+ if (
96
+ previous.listener === listener &&
97
+ (previous.state === state ||
98
+ (event === 'update' &&
99
+ Object.is(select(previous.state), select(state))))
100
+ ) {
101
+ return previous;
82
102
  }
103
+
104
+ return { listener, state };
83
105
  },
84
- [navigation, stateListener]
106
+ undefined,
107
+ () => ({ listener, state: getState() })
85
108
  );
86
109
 
87
- const getSnapshot = navigation
88
- ? navigation.getState
89
- : stateListener?.getState;
110
+ let state = entry.state;
90
111
 
91
- if (getSnapshot == null) {
92
- throw new Error("Couldn't get the navigation state. This is not expected.");
93
- }
112
+ const committedRef = React.useRef<{
113
+ entry: NavigationStateEntry | null;
114
+ state: NavigationState<ParamListBase>;
115
+ select: (state: NavigationState<ParamListBase>) => unknown;
116
+ }>({ entry: null, state, select });
94
117
 
95
- if (typeof selector !== 'function') {
96
- throw new Error(
97
- `A selector function must be provided (got ${typeof selector}).`
98
- );
118
+ React.useInsertionEffect(() => {
119
+ committedRef.current = { entry, state, select };
120
+ });
121
+
122
+ const snapshot = getSnapshot();
123
+
124
+ const behind =
125
+ state !== snapshot &&
126
+ // A different listener means the state belongs to another navigator.
127
+ // This can happen when the passed route name changes.
128
+ (entry.listener !== listener ||
129
+ // An entry that advanced past the last commit is at least as new as the snapshot.
130
+ // An entry that didn't advance while the committed state changed is older.
131
+ entry === committedRef.current.entry);
132
+
133
+ if (behind) {
134
+ state = snapshot;
135
+ dispatch('commit');
99
136
  }
100
137
 
101
- const value = useSyncExternalStoreWithSelector(
102
- subscribe,
103
- getSnapshot,
104
- getSnapshot,
105
- // @ts-expect-error we can't infer the type here
106
- selector
107
- );
138
+ React.useLayoutEffect(() => {
139
+ const check = (event: NavigationStateEvent) => {
140
+ const { state, select } = committedRef.current;
141
+
142
+ // An 'update' event follows the latest state in the lane the update was made in.
143
+ // A 'commit' event syncs the reducer to the state committed by the navigator.
144
+ const target = event === 'update' ? getState() : getSnapshot();
145
+
146
+ if (state !== target && !Object.is(select(state), select(target))) {
147
+ dispatch(event);
148
+ }
149
+ };
108
150
 
109
- return value;
151
+ const unsubscribe = subscribe(check);
152
+
153
+ // The reducer may not match the committed state when the subscription starts.
154
+ // This happens on mount during a transition or an update before subscribing.
155
+ check('commit');
156
+
157
+ return unsubscribe;
158
+ }, [getState, getSnapshot, subscribe]);
159
+
160
+ return select(state);
110
161
  }
111
162
 
112
163
  export function NavigationStateListenerProvider({
113
164
  state,
165
+ getState,
166
+ subscribe,
114
167
  children,
115
168
  }: {
116
169
  state: NavigationState<ParamListBase>;
170
+ getState: () => NavigationState<ParamListBase>;
171
+ subscribe: (callback: () => void) => () => void;
117
172
  children: React.ReactNode;
118
173
  }) {
119
- const listeners = React.useRef<(() => void)[]>([]);
174
+ const snapshotRef = React.useRef(state);
120
175
 
121
- const getState = useLatestCallback(() => state);
122
-
123
- const subscribe = useLatestCallback((callback: () => void) => {
124
- listeners.current.push(callback);
176
+ React.useInsertionEffect(() => {
177
+ snapshotRef.current = state;
178
+ }, [state]);
125
179
 
126
- return () => {
127
- listeners.current = listeners.current.filter((cb) => cb !== callback);
128
- };
129
- });
180
+ const [listeners] = React.useState(
181
+ () => new Set<(event: NavigationStateEvent) => void>()
182
+ );
130
183
 
131
- React.useEffect(() => {
132
- listeners.current.forEach((callback) => callback());
133
- }, [state]);
184
+ React.useLayoutEffect(() => {
185
+ listeners.forEach((listener) => listener('commit'));
186
+ }, [listeners, state]);
134
187
 
135
188
  const context = React.useMemo(
136
189
  () => ({
137
190
  getState,
138
- subscribe,
191
+ getSnapshot: () => snapshotRef.current,
192
+ subscribe: (callback: (event: NavigationStateEvent) => void) => {
193
+ const unsubscribe = subscribe(() => callback('update'));
194
+
195
+ listeners.add(callback);
196
+
197
+ return () => {
198
+ listeners.delete(callback);
199
+ unsubscribe();
200
+ };
201
+ },
139
202
  }),
140
- [getState, subscribe]
203
+ [getState, listeners, subscribe]
141
204
  );
142
205
 
143
206
  return (
@@ -147,6 +210,42 @@ export function NavigationStateListenerProvider({
147
210
  );
148
211
  }
149
212
 
150
- const NavigationStateListenerContext = React.createContext<
213
+ export function NamedNavigationStateListenerProvider({
214
+ name,
215
+ children,
216
+ }: {
217
+ name: string;
218
+ children: React.ReactNode;
219
+ }) {
220
+ const listener = React.use(NavigationStateListenerContext);
221
+
222
+ if (listener == null) {
223
+ throw new Error(
224
+ "Couldn't find a navigation state listener. This is likely because the navigator doesn't render its content under 'NavigationContent'."
225
+ );
226
+ }
227
+
228
+ const parents = React.use(NamedNavigationStateListenerListContext);
229
+
230
+ const listeners = React.useMemo(
231
+ () => ({
232
+ ...parents,
233
+ [name]: listener,
234
+ }),
235
+ [listener, name, parents]
236
+ );
237
+
238
+ return (
239
+ <NamedNavigationStateListenerListContext.Provider value={listeners}>
240
+ {children}
241
+ </NamedNavigationStateListenerListContext.Provider>
242
+ );
243
+ }
244
+
245
+ export const NavigationStateListenerContext = React.createContext<
151
246
  NavigationStateListener | undefined
152
247
  >(undefined);
248
+
249
+ export const NamedNavigationStateListenerListContext = React.createContext<
250
+ Record<string, NavigationStateListener> | undefined
251
+ >(undefined);
@@ -7,20 +7,23 @@ import type {
7
7
  } from '@react-navigation/routers';
8
8
  import * as React from 'react';
9
9
 
10
+ import { ConsumedParamsContext } from './ConsumedParamsContext';
11
+ import { getLoaderForStateChange } from './DataLoading';
10
12
  import {
11
13
  type ChildActionListener,
12
14
  type ChildBeforeRemoveListener,
13
15
  NavigationBuilderContext,
14
16
  } from './NavigationBuilderContext';
17
+ import { StaticTreeContext } from './StaticTreeContext';
15
18
  import type { EventMapCore } from './types';
16
19
  import type { NavigationEventEmitter } from './useEventEmitter';
17
20
  import { shouldPreventRemove, useOnPreventRemove } from './useOnPreventRemove';
18
21
 
19
- type Options = {
20
- router: Router<NavigationState, NavigationAction>;
21
- key?: string;
22
- getState: () => NavigationState;
23
- setState: (state: NavigationState | PartialState<NavigationState>) => void;
22
+ type Options<State extends NavigationState> = {
23
+ router: Router<State, NavigationAction>;
24
+ key?: string | undefined;
25
+ getState: () => State;
26
+ setState: (state: State | PartialState<State>) => void;
24
27
  actionListeners: ChildActionListener[];
25
28
  beforeRemoveListeners: Record<string, ChildBeforeRemoveListener | undefined>;
26
29
  routerConfigOptions: RouterConfigOptions;
@@ -36,7 +39,7 @@ type Options = {
36
39
  *
37
40
  * When the action handler handles as action, it returns `true`, otherwise `false`.
38
41
  */
39
- export function useOnAction({
42
+ export function useOnAction<State extends NavigationState>({
40
43
  router,
41
44
  getState,
42
45
  setState,
@@ -45,18 +48,22 @@ export function useOnAction({
45
48
  beforeRemoveListeners,
46
49
  routerConfigOptions,
47
50
  emitter,
48
- }: Options) {
51
+ }: Options<State>) {
49
52
  const {
50
53
  onAction: onActionParent,
51
54
  onRouteFocus: onRouteFocusParent,
52
55
  addListener: addListenerParent,
53
56
  onDispatchAction,
54
- } = React.useContext(NavigationBuilderContext);
57
+ flushUpdates,
58
+ } = React.use(NavigationBuilderContext);
59
+
60
+ const tree = React.use(StaticTreeContext);
61
+ const consumedParams = React.use(ConsumedParamsContext);
55
62
 
56
63
  const routerConfigOptionsRef =
57
64
  React.useRef<RouterConfigOptions>(routerConfigOptions);
58
65
 
59
- React.useEffect(() => {
66
+ React.useInsertionEffect(() => {
60
67
  routerConfigOptionsRef.current = routerConfigOptions;
61
68
  });
62
69
 
@@ -65,6 +72,11 @@ export function useOnAction({
65
72
  action: NavigationAction,
66
73
  visitedNavigators: Set<string> = new Set<string>()
67
74
  ) => {
75
+ // Apply any pending state updates scheduled during render before handling the action
76
+ // Otherwise the action will be handled with an outdated state,
77
+ // and the pending updates will overwrite the changes from the action
78
+ flushUpdates();
79
+
68
80
  const state = getState();
69
81
 
70
82
  // Since actions can bubble both up and down, they could come to the same navigator again
@@ -87,9 +99,18 @@ export function useOnAction({
87
99
  result =
88
100
  result === null && action.target === state.key ? state : result;
89
101
 
90
- if (result !== null) {
91
- onDispatchAction(action, state === result);
102
+ if (result !== null && result.stale !== false) {
103
+ // Some actions (e.g. `RESET`) may return a stale state from the router
104
+ // We rehydrate it before storing so the stored state is always usable
105
+ // Otherwise anything reading the state before the next render
106
+ // (e.g. another dispatch immediately after) would get an outdated state
107
+ result = router.getRehydratedState(
108
+ result,
109
+ routerConfigOptionsRef.current
110
+ );
111
+ }
92
112
 
113
+ if (result !== null) {
93
114
  if (state !== result) {
94
115
  const isPrevented = shouldPreventRemove(
95
116
  emitter,
@@ -100,10 +121,34 @@ export function useOnAction({
100
121
  );
101
122
 
102
123
  if (isPrevented) {
124
+ onDispatchAction(action, true);
125
+
103
126
  return true;
104
127
  }
105
128
 
129
+ if (getState() !== state) {
130
+ // Listeners for 'beforeRemove' may dispatch actions that change the state
131
+ // Then the result we have is outdated and would overwrite those changes
132
+ // So we handle the action again to calculate the result with the latest state
133
+ return onAction(action, new Set<string>());
134
+ }
135
+
136
+ onDispatchAction(action, false);
137
+
138
+ if (tree) {
139
+ const loader = getLoaderForStateChange(
140
+ tree,
141
+ result,
142
+ state,
143
+ consumedParams
144
+ );
145
+
146
+ loader?.();
147
+ }
148
+
106
149
  setState(result);
150
+ } else {
151
+ onDispatchAction(action, true);
107
152
  }
108
153
 
109
154
  if (onRouteFocusParent !== undefined) {
@@ -132,7 +177,7 @@ export function useOnAction({
132
177
  for (let i = actionListeners.length - 1; i >= 0; i--) {
133
178
  const listener = actionListeners[i];
134
179
 
135
- if (listener(action, visitedNavigators)) {
180
+ if (listener?.(action, visitedNavigators)) {
136
181
  return true;
137
182
  }
138
183
  }
@@ -143,7 +188,9 @@ export function useOnAction({
143
188
  [
144
189
  actionListeners,
145
190
  beforeRemoveListeners,
191
+ consumedParams,
146
192
  emitter,
193
+ flushUpdates,
147
194
  getState,
148
195
  key,
149
196
  onActionParent,
@@ -151,6 +198,7 @@ export function useOnAction({
151
198
  onRouteFocusParent,
152
199
  router,
153
200
  setState,
201
+ tree,
154
202
  ]
155
203
  );
156
204
 
@@ -160,7 +208,7 @@ export function useOnAction({
160
208
  beforeRemoveListeners,
161
209
  });
162
210
 
163
- React.useEffect(
211
+ React.useInsertionEffect(
164
212
  () => addListenerParent?.('action', onAction),
165
213
  [addListenerParent, onAction]
166
214
  );