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

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 (90) hide show
  1. package/lib/module/BaseNavigationContainer.js +128 -80
  2. package/lib/module/BaseNavigationContainer.js.map +1 -1
  3. package/lib/module/DataLoading.js +3 -1
  4. package/lib/module/DataLoading.js.map +1 -1
  5. package/lib/module/NavigationBuilderContext.js +2 -1
  6. package/lib/module/NavigationBuilderContext.js.map +1 -1
  7. package/lib/module/NavigationIndependentTree.js +13 -6
  8. package/lib/module/NavigationIndependentTree.js.map +1 -1
  9. package/lib/module/NavigationProvider.js +10 -1
  10. package/lib/module/NavigationProvider.js.map +1 -1
  11. package/lib/module/NavigationRootContext.js +8 -0
  12. package/lib/module/NavigationRootContext.js.map +1 -0
  13. package/lib/module/StaticNavigation.js +54 -19
  14. package/lib/module/StaticNavigation.js.map +1 -1
  15. package/lib/module/createNavigationContainerRef.js +1 -1
  16. package/lib/module/createNavigationContainerRef.js.map +1 -1
  17. package/lib/module/index.js +1 -1
  18. package/lib/module/index.js.map +1 -1
  19. package/lib/module/internal.js +2 -0
  20. package/lib/module/internal.js.map +1 -0
  21. package/lib/module/types.js +0 -1
  22. package/lib/module/types.js.map +1 -1
  23. package/lib/module/useDescriptors.js +4 -4
  24. package/lib/module/useDescriptors.js.map +1 -1
  25. package/lib/module/useKeyedChildListeners.js +1 -0
  26. package/lib/module/useKeyedChildListeners.js.map +1 -1
  27. package/lib/module/useNavigation.js +2 -2
  28. package/lib/module/useNavigation.js.map +1 -1
  29. package/lib/module/useNavigationBuilder.js +11 -4
  30. package/lib/module/useNavigationBuilder.js.map +1 -1
  31. package/lib/module/useNavigationCache.js +23 -31
  32. package/lib/module/useNavigationCache.js.map +1 -1
  33. package/lib/module/useNavigationHelpers.js +18 -37
  34. package/lib/module/useNavigationHelpers.js.map +1 -1
  35. package/lib/module/useNavigationState.js +13 -6
  36. package/lib/module/useNavigationState.js.map +1 -1
  37. package/lib/module/useOnGetNavigation.js +26 -0
  38. package/lib/module/useOnGetNavigation.js.map +1 -0
  39. package/lib/typescript/src/BaseNavigationContainer.d.ts.map +1 -1
  40. package/lib/typescript/src/DataLoading.d.ts.map +1 -1
  41. package/lib/typescript/src/NavigationBuilderContext.d.ts +4 -1
  42. package/lib/typescript/src/NavigationBuilderContext.d.ts.map +1 -1
  43. package/lib/typescript/src/NavigationIndependentTree.d.ts.map +1 -1
  44. package/lib/typescript/src/NavigationProvider.d.ts +9 -5
  45. package/lib/typescript/src/NavigationProvider.d.ts.map +1 -1
  46. package/lib/typescript/src/NavigationRootContext.d.ts +8 -0
  47. package/lib/typescript/src/NavigationRootContext.d.ts.map +1 -0
  48. package/lib/typescript/src/StaticNavigation.d.ts.map +1 -1
  49. package/lib/typescript/src/createNavigationContainerRef.d.ts.map +1 -1
  50. package/lib/typescript/src/index.d.ts +1 -1
  51. package/lib/typescript/src/index.d.ts.map +1 -1
  52. package/lib/typescript/src/internal.d.ts +3 -0
  53. package/lib/typescript/src/internal.d.ts.map +1 -0
  54. package/lib/typescript/src/types.d.ts +3 -6
  55. package/lib/typescript/src/types.d.ts.map +1 -1
  56. package/lib/typescript/src/useKeyedChildListeners.d.ts +1 -0
  57. package/lib/typescript/src/useKeyedChildListeners.d.ts.map +1 -1
  58. package/lib/typescript/src/useNavigationBuilder.d.ts.map +1 -1
  59. package/lib/typescript/src/useNavigationCache.d.ts.map +1 -1
  60. package/lib/typescript/src/useNavigationHelpers.d.ts +1 -2
  61. package/lib/typescript/src/useNavigationHelpers.d.ts.map +1 -1
  62. package/lib/typescript/src/useNavigationState.d.ts +2 -1
  63. package/lib/typescript/src/useNavigationState.d.ts.map +1 -1
  64. package/lib/typescript/src/useOnGetNavigation.d.ts +12 -0
  65. package/lib/typescript/src/useOnGetNavigation.d.ts.map +1 -0
  66. package/package.json +9 -3
  67. package/src/BaseNavigationContainer.tsx +181 -94
  68. package/src/DataLoading.tsx +4 -1
  69. package/src/NavigationBuilderContext.tsx +10 -1
  70. package/src/NavigationIndependentTree.tsx +13 -7
  71. package/src/NavigationProvider.tsx +13 -6
  72. package/src/NavigationRootContext.tsx +11 -0
  73. package/src/StaticNavigation.tsx +77 -19
  74. package/src/createNavigationContainerRef.tsx +0 -2
  75. package/src/index.tsx +1 -0
  76. package/src/internal.tsx +2 -0
  77. package/src/types.tsx +3 -7
  78. package/src/useDescriptors.tsx +3 -3
  79. package/src/useKeyedChildListeners.tsx +1 -0
  80. package/src/useNavigation.tsx +2 -2
  81. package/src/useNavigationBuilder.tsx +11 -4
  82. package/src/useNavigationCache.tsx +24 -33
  83. package/src/useNavigationHelpers.tsx +19 -38
  84. package/src/useNavigationState.tsx +16 -8
  85. package/src/useOnGetNavigation.tsx +29 -0
  86. package/lib/module/checkSerializable.js +0 -51
  87. package/lib/module/checkSerializable.js.map +0 -1
  88. package/lib/typescript/src/checkSerializable.d.ts +0 -14
  89. package/lib/typescript/src/checkSerializable.d.ts.map +0 -1
  90. package/src/checkSerializable.tsx +0 -90
@@ -0,0 +1,11 @@
1
+ import type { ParamListBase } from '@react-navigation/routers';
2
+ import * as React from 'react';
3
+
4
+ import type { GenericNavigation } from './types';
5
+
6
+ /**
7
+ * Context which holds the navigation object for components outside a screen.
8
+ */
9
+ export const NavigationRootContext = React.createContext<
10
+ GenericNavigation<ParamListBase> | undefined
11
+ >(undefined);
@@ -678,6 +678,9 @@ const MemoizedScreen = React.memo(
678
678
 
679
679
  MemoizedScreen.displayName = 'Memo(Screen)';
680
680
 
681
+ const hasPropertyGetter = (object: object, property: PropertyKey) =>
682
+ Object.getOwnPropertyDescriptor(object, property)?.get != null;
683
+
681
684
  const getItemsFromScreens = (
682
685
  Screen: React.ComponentType<any>,
683
686
  screens: StaticConfigScreens<any, any, any, any, any>
@@ -688,18 +691,57 @@ const getItemsFromScreens = (
688
691
  let useIf: (() => boolean) | undefined;
689
692
 
690
693
  let isNavigator = false;
694
+ let getComponent: (() => React.ComponentType<any>) | undefined;
691
695
 
692
696
  if ('screen' in item) {
693
- const { screen, if: _if, ...rest } = item;
697
+ if (hasPropertyGetter(item, 'screen')) {
698
+ const rest: Record<string, unknown> = {};
699
+
700
+ // Iterate over the keys to avoid triggering the getter for 'screen'
701
+ for (const key of Object.keys(item)) {
702
+ if (key === 'screen' || key === 'if') {
703
+ continue;
704
+ }
705
+
706
+ rest[key] = Reflect.get(item, key);
707
+ }
708
+
709
+ useIf = item.if;
710
+ props = rest;
694
711
 
695
- useIf = _if;
696
- props = rest;
712
+ getComponent = () => {
713
+ const screen = item.screen;
697
714
 
698
- if (isValidElementType(screen)) {
699
- component = screen;
700
- } else if ('config' in screen) {
701
- isNavigator = true;
702
- component = screen.getComponent();
715
+ if (isValidElementType(screen)) {
716
+ return screen;
717
+ }
718
+
719
+ if (
720
+ screen != null &&
721
+ typeof screen === 'object' &&
722
+ 'config' in screen
723
+ ) {
724
+ throw new Error(
725
+ `Got a navigator object for the screen '${name}' instead of a component. A navigator cannot be specified when using a getter.`
726
+ );
727
+ }
728
+
729
+ throw new Error(
730
+ `Got an invalid screen component for the screen '${name}'. This can happen if you passed 'undefined'. You likely forgot to export your component from the file it's defined in, or mixed up default import and named import when importing.`
731
+ );
732
+ };
733
+ } else {
734
+ const { screen, if: _if, ...rest } = item;
735
+
736
+ useIf = _if;
737
+ props = rest;
738
+
739
+ if (isValidElementType(screen)) {
740
+ component = screen;
741
+ } else if ('config' in screen) {
742
+ isNavigator = true;
743
+ component = screen.getComponent();
744
+ }
703
745
  }
704
746
  } else if (isValidElementType(item)) {
705
747
  component = item;
@@ -708,18 +750,29 @@ const getItemsFromScreens = (
708
750
  component = item.getComponent();
709
751
  }
710
752
 
711
- if (component == null) {
753
+ let children: (() => React.JSX.Element) | undefined;
754
+
755
+ if (getComponent != null) {
756
+ let element: React.JSX.Element | undefined;
757
+
758
+ children = () =>
759
+ (element ??= <MemoizedScreen component={getComponent()} />);
760
+ } else if (component != null) {
761
+ const element = isNavigator ? (
762
+ React.createElement(component, {})
763
+ ) : (
764
+ <MemoizedScreen component={component} />
765
+ );
766
+
767
+ children = () => element;
768
+ }
769
+
770
+ if (children == null) {
712
771
  throw new Error(
713
772
  `Couldn't find a 'screen' property for the screen '${name}'. This can happen if you passed 'undefined'. You likely forgot to export your component from the file it's defined in, or mixed up default import and named import when importing.`
714
773
  );
715
774
  }
716
775
 
717
- const element = isNavigator ? (
718
- React.createElement(component, {})
719
- ) : (
720
- <MemoizedScreen component={component} />
721
- );
722
-
723
776
  return () => {
724
777
  const shouldRender = useIf == null || useIf();
725
778
 
@@ -729,7 +782,7 @@ const getItemsFromScreens = (
729
782
 
730
783
  return (
731
784
  <Screen key={name} name={name} {...props}>
732
- {() => element}
785
+ {children}
733
786
  </Screen>
734
787
  );
735
788
  };
@@ -895,7 +948,7 @@ type PathConfigForStaticNavigation = Omit<
895
948
  // Mark screens that resolve to the same pattern and same component or navigator
896
949
  const markSharedPathConfigs = (
897
950
  screens: PathConfigMapForStaticNavigation,
898
- sources: WeakMap<PathConfigForStaticNavigation, ScreenValueForPathConfig>,
951
+ sources: WeakMap<PathConfigForStaticNavigation, ScreenForPathConfig>,
899
952
  blocked: WeakSet<PathConfigForStaticNavigation>
900
953
  ) => {
901
954
  const candidates = new Map<string, PathConfigForStaticNavigation[]>();
@@ -992,7 +1045,7 @@ export function createPathConfigForStaticNavigation<ParamList extends {}>(
992
1045
  const blocked = new WeakSet<PathConfigForStaticNavigation>();
993
1046
  const sources = new WeakMap<
994
1047
  PathConfigForStaticNavigation,
995
- ScreenValueForPathConfig
1048
+ ScreenForPathConfig
996
1049
  >();
997
1050
 
998
1051
  const createPathConfigForTree = (
@@ -1043,7 +1096,11 @@ export function createPathConfigForStaticNavigation<ParamList extends {}>(
1043
1096
 
1044
1097
  sources.set(
1045
1098
  screenConfig,
1046
- typeof item === 'object' && 'screen' in item ? item.screen : item
1099
+ typeof item === 'object' &&
1100
+ 'screen' in item &&
1101
+ !hasPropertyGetter(item, 'screen')
1102
+ ? item.screen
1103
+ : item
1047
1104
  );
1048
1105
 
1049
1106
  const normalizePath = (path: string) =>
@@ -1129,6 +1186,7 @@ export function createPathConfigForStaticNavigation<ParamList extends {}>(
1129
1186
  !hasExplicitScreens &&
1130
1187
  !hasDisabledLinking &&
1131
1188
  'screen' in item &&
1189
+ !hasPropertyGetter(item, 'screen') &&
1132
1190
  'config' in item.screen &&
1133
1191
  (item.screen.config.screens || item.screen.config.groups)
1134
1192
  ) {
@@ -19,11 +19,9 @@ export function createNavigationContainerRef<
19
19
  'removeListener',
20
20
  'resetRoot',
21
21
  'dispatch',
22
- 'isFocused',
23
22
  'canGoBack',
24
23
  'getRootState',
25
24
  'getState',
26
- 'getParent',
27
25
  'getCurrentRoute',
28
26
  'getCurrentOptions',
29
27
  ] as const;
package/src/index.tsx CHANGED
@@ -19,6 +19,7 @@ export { NavigationHelpersContext } from './NavigationHelpersContext';
19
19
  export { NavigationIndependentTree } from './NavigationIndependentTree';
20
20
  export { NavigationMetaContext } from './NavigationMetaContext';
21
21
  export {
22
+ IsScreenContext,
22
23
  NavigationContext,
23
24
  NavigationProvider,
24
25
  NavigationRouteContext,
@@ -0,0 +1,2 @@
1
+ // eslint-disable-next-line @typescript-eslint/no-empty-interface
2
+ export interface Theme {}
package/src/types.tsx CHANGED
@@ -10,6 +10,7 @@ import type {
10
10
  } from '@react-navigation/routers';
11
11
  import type * as React from 'react';
12
12
 
13
+ import type { Theme as InternalTheme } from './internal';
13
14
  import type {
14
15
  FlatType,
15
16
  KeyOf,
@@ -51,8 +52,7 @@ export interface RootNavigator {}
51
52
  * }
52
53
  * ```
53
54
  */
54
- // eslint-disable-next-line @typescript-eslint/no-empty-interface
55
- export interface Theme {}
55
+ export interface Theme extends InternalTheme {}
56
56
 
57
57
  export type RootParamList =
58
58
  RootNavigator extends PrivateValueStore<
@@ -1360,7 +1360,7 @@ type NavigationListForGroups<ParentList, Groups> = Groups extends {}
1360
1360
 
1361
1361
  export type NavigationContainerRef<ParamList extends {}> = Omit<
1362
1362
  NavigationHelpers<ParamList>,
1363
- keyof NavigationHelpersRoute<{}>
1363
+ keyof NavigationHelpersRoute<{}> | 'isFocused'
1364
1364
  > &
1365
1365
  NavigationHelpersRoute<{}> &
1366
1366
  EventConsumer<NavigationContainerEventMap> & {
@@ -1386,10 +1386,6 @@ export type NavigationContainerRef<ParamList extends {}> = Omit<
1386
1386
  * Whether the navigation container is ready to handle actions.
1387
1387
  */
1388
1388
  isReady(): boolean;
1389
- /**
1390
- * Stub function for setOptions on navigation object for use with useNavigation.
1391
- */
1392
- setOptions(): never;
1393
1389
  };
1394
1390
 
1395
1391
  export type NavigationContainerRefWithCurrent<ParamList extends {}> =
@@ -125,8 +125,8 @@ export function useDescriptors<
125
125
  onOptionsChange,
126
126
  scheduleUpdate,
127
127
  flushUpdates,
128
- stackRef,
129
128
  getIsStateEmitted,
129
+ withStackTrace,
130
130
  } = React.use(NavigationBuilderContext);
131
131
 
132
132
  const context = React.useMemo(
@@ -142,7 +142,7 @@ export function useDescriptors<
142
142
  getIsStateEmitted,
143
143
  scheduleUpdate,
144
144
  flushUpdates,
145
- stackRef,
145
+ withStackTrace,
146
146
  }),
147
147
  [
148
148
  navigation,
@@ -156,7 +156,7 @@ export function useDescriptors<
156
156
  getIsStateEmitted,
157
157
  scheduleUpdate,
158
158
  flushUpdates,
159
- stackRef,
159
+ withStackTrace,
160
160
  ]
161
161
  );
162
162
 
@@ -14,6 +14,7 @@ export function useKeyedChildListeners() {
14
14
  }>(
15
15
  Object.assign(Object.create(null), {
16
16
  getState: {},
17
+ getNavigation: {},
17
18
  beforeRemove: {},
18
19
  })
19
20
  );
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
 
3
- import { NavigationContainerRefContext } from './NavigationContainerRefContext';
4
3
  import { NavigationContext } from './NavigationProvider';
4
+ import { NavigationRootContext } from './NavigationRootContext';
5
5
  import type {
6
6
  GenericNavigation,
7
7
  NavigationListForNavigator,
@@ -30,7 +30,7 @@ export function useNavigation<
30
30
  keyof NavigationListForNested<Navigator>,
31
31
  >(name: RouteName): NavigationListForNested<Navigator>[RouteName];
32
32
  export function useNavigation(name?: string): unknown {
33
- const root = React.use(NavigationContainerRefContext);
33
+ const root = React.use(NavigationRootContext);
34
34
  const navigation = React.use(NavigationContext);
35
35
 
36
36
  if (name === undefined) {
@@ -22,7 +22,7 @@ import { isArrayEqual } from './isArrayEqual';
22
22
  import { NavigationBuilderContext } from './NavigationBuilderContext';
23
23
  import { NavigationHelpersContext } from './NavigationHelpersContext';
24
24
  import { NavigationMetaContext } from './NavigationMetaContext';
25
- import { NavigationRouteContext } from './NavigationProvider';
25
+ import { IsScreenContext, NavigationRouteContext } from './NavigationProvider';
26
26
  import { NavigationStateContext } from './NavigationStateContext';
27
27
  import { PreventRemoveProvider } from './PreventRemoveProvider';
28
28
  import { Screen } from './Screen';
@@ -47,6 +47,7 @@ import { useLazyValue } from './useLazyValue';
47
47
  import { useNavigationHelpers } from './useNavigationHelpers';
48
48
  import { NavigationStateListenerProvider } from './useNavigationState';
49
49
  import { useOnAction } from './useOnAction';
50
+ import { useOnGetNavigation } from './useOnGetNavigation';
50
51
  import { useOnGetState } from './useOnGetState';
51
52
  import { useOnRouteFocus } from './useOnRouteFocus';
52
53
  import { useRegisterNavigator } from './useRegisterNavigator';
@@ -1005,7 +1006,6 @@ export function useNavigationBuilder<
1005
1006
  onAction,
1006
1007
  onUnhandledAction,
1007
1008
  getState,
1008
- state,
1009
1009
  emitter,
1010
1010
  router,
1011
1011
  });
@@ -1020,6 +1020,8 @@ export function useNavigationBuilder<
1020
1020
  getStateListeners: keyedListeners.getState,
1021
1021
  });
1022
1022
 
1023
+ useOnGetNavigation({ navigation });
1024
+
1023
1025
  const descriptors = useDescriptors<
1024
1026
  State,
1025
1027
  ActionHelpers,
@@ -1064,12 +1066,17 @@ export function useNavigationBuilder<
1064
1066
  <NavigationMetaContext.Provider value={undefined}>
1065
1067
  <NavigationHelpersContext.Provider value={navigation}>
1066
1068
  <NavigationStateListenerProvider
1069
+ isSynced={!shouldUpdate}
1067
1070
  state={state}
1068
- getState={navigation.getState}
1071
+ getState={getState}
1069
1072
  subscribe={subscribe}
1070
1073
  >
1071
1074
  <FocusedRouteKeyContext.Provider value={focusedRoute.key}>
1072
- <PreventRemoveProvider>{element}</PreventRemoveProvider>
1075
+ <PreventRemoveProvider>
1076
+ <IsScreenContext.Provider value={false}>
1077
+ {element}
1078
+ </IsScreenContext.Provider>
1079
+ </PreventRemoveProvider>
1073
1080
  </FocusedRouteKeyContext.Provider>
1074
1081
  </NavigationStateListenerProvider>
1075
1082
  </NavigationHelpersContext.Provider>
@@ -72,8 +72,9 @@ export function useNavigationCache<
72
72
  router,
73
73
  emitter,
74
74
  }: Options<State, ScreenOptions, EventMap>) {
75
- const parentNavigationHelpers = React.use(NavigationContext);
76
- const { stackRef } = React.use(NavigationBuilderContext);
75
+ const parentNavigation = React.use(NavigationContext);
76
+
77
+ const { withStackTrace } = React.use(NavigationBuilderContext);
77
78
 
78
79
  // Cache object which holds navigation objects for each screen
79
80
  // We use `React.useMemo` instead of `React.useRef` coz we want to invalidate it when deps change
@@ -88,7 +89,7 @@ export function useNavigationCache<
88
89
  >,
89
90
  }),
90
91
  // eslint-disable-next-line react-hooks/exhaustive-deps
91
- [getState, parentNavigationHelpers, navigation, setOptions, emitter]
92
+ [getState, parentNavigation, navigation, setOptions, emitter]
92
93
  );
93
94
 
94
95
  const next = routes.reduce<
@@ -100,34 +101,12 @@ export function useNavigationCache<
100
101
  // If a cached navigation object already exists, reuse it
101
102
  acc[route.key] = previous;
102
103
  } else {
103
- const withStack = (callback: () => void) => {
104
- let isStackSet = false;
105
-
106
- try {
107
- if (
108
- process.env.NODE_ENV !== 'production' &&
109
- stackRef &&
110
- !stackRef.current
111
- ) {
112
- // Capture the stack trace for devtools
113
- stackRef.current = new Error().stack;
114
- isStackSet = true;
115
- }
116
-
117
- callback();
118
- } finally {
119
- if (isStackSet && stackRef) {
120
- stackRef.current = undefined;
121
- }
122
- }
123
- };
124
-
125
104
  const dispatch = (
126
105
  thunk:
127
106
  | NavigationAction
128
107
  | ((state: State) => NavigationAction | null | undefined)
129
108
  ) => {
130
- withStack(() =>
109
+ withStackTrace(dispatch, () => {
131
110
  React.startTransition(() => {
132
111
  const action =
133
112
  typeof thunk === 'function' ? thunk(getState()) : thunk;
@@ -135,8 +114,8 @@ export function useNavigationCache<
135
114
  if (action != null) {
136
115
  navigation.dispatch({ source: route.key, ...action });
137
116
  }
138
- })
139
- );
117
+ });
118
+ });
140
119
  };
141
120
 
142
121
  const actions = {
@@ -146,9 +125,13 @@ export function useNavigationCache<
146
125
 
147
126
  const helpers = Object.keys(actions).reduce<Record<string, () => void>>(
148
127
  (acc, name) => {
149
- acc[name] = (...args: any) =>
150
- // @ts-expect-error: name is a valid key, but TypeScript is dumb
151
- dispatch(actions[name](...args));
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;
152
135
 
153
136
  return acc;
154
137
  },
@@ -172,10 +155,18 @@ export function useNavigationCache<
172
155
  }
173
156
 
174
157
  if (routeName !== undefined) {
175
- return parentNavigationHelpers?.getParent(routeName);
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?`
163
+ );
164
+ }
165
+
166
+ return parent;
176
167
  }
177
168
 
178
- return parentNavigationHelpers;
169
+ return parentNavigation;
179
170
  },
180
171
  setOptions: (options: Partial<ScreenOptions>) => {
181
172
  setOptions((o) => ({
@@ -7,6 +7,7 @@ import {
7
7
  } from '@react-navigation/routers';
8
8
  import * as React from 'react';
9
9
 
10
+ import { NavigationBuilderContext } from './NavigationBuilderContext';
10
11
  import { NavigationContext } from './NavigationProvider';
11
12
  import { type NavigationHelpers, PrivateValueStore } from './types';
12
13
  import type { NavigationEventEmitter } from './useEventEmitter';
@@ -19,7 +20,6 @@ type Options<State extends NavigationState, Action extends NavigationAction> = {
19
20
  onAction: (action: NavigationAction) => boolean;
20
21
  onUnhandledAction: (action: NavigationAction) => void;
21
22
  getState: () => State;
22
- state: State;
23
23
  emitter: NavigationEventEmitter<any>;
24
24
  router: Router<State, Action>;
25
25
  };
@@ -37,38 +37,23 @@ export function useNavigationHelpers<
37
37
  onAction,
38
38
  onUnhandledAction,
39
39
  getState,
40
- state,
41
40
  emitter,
42
41
  router,
43
42
  }: Options<State, Action>) {
44
43
  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
- });
44
+ const { withStackTrace } = React.use(NavigationBuilderContext);
62
45
 
63
46
  return React.useMemo(() => {
64
47
  const dispatch = (op: Action | ((state: State) => Action)) => {
65
- const action = typeof op === 'function' ? op(getState()) : op;
48
+ withStackTrace(dispatch, () => {
49
+ const action = typeof op === 'function' ? op(getState()) : op;
66
50
 
67
- const handled = onAction(action);
51
+ const handled = onAction(action);
68
52
 
69
- if (!handled) {
70
- onUnhandledAction?.(action);
71
- }
53
+ if (!handled) {
54
+ onUnhandledAction?.(action);
55
+ }
56
+ });
72
57
  };
73
58
 
74
59
  const actions = {
@@ -77,8 +62,15 @@ export function useNavigationHelpers<
77
62
  };
78
63
 
79
64
  const helpers = Object.keys(actions).reduce((acc, name) => {
65
+ const helper = (...args: any) =>
66
+ withStackTrace(helper, () =>
67
+ // @ts-expect-error: name is a valid key, but TypeScript is dumb
68
+ dispatch(actions[name](...args))
69
+ );
70
+
80
71
  // @ts-expect-error: name is a valid key, but TypeScript is dumb
81
- acc[name] = (...args: any) => dispatch(actions[name](...args));
72
+ acc[name] = helper;
73
+
82
74
  return acc;
83
75
  }, {} as ActionHelpers);
84
76
 
@@ -103,18 +95,7 @@ export function useNavigationHelpers<
103
95
  false
104
96
  );
105
97
  },
106
- getState: (): State => {
107
- const current = getState();
108
- const pending = stateRef.current;
109
-
110
- // FIXME: Workaround for when the state is read during render
111
- // Apart from subscriptions, `getState` should never be called during render
112
- if (pending != null && pending.base === current) {
113
- return pending.state;
114
- }
115
-
116
- return current;
117
- },
98
+ getState,
118
99
  } as NavigationHelpers<ParamListBase, EventMap> & ActionHelpers;
119
100
 
120
101
  return navigationHelpers;
@@ -125,6 +106,6 @@ export function useNavigationHelpers<
125
106
  getState,
126
107
  onAction,
127
108
  onUnhandledAction,
128
- stateRef,
109
+ withStackTrace,
129
110
  ]);
130
111
  }
@@ -161,22 +161,18 @@ export function useNavigationState(...args: unknown[]): unknown {
161
161
  }
162
162
 
163
163
  export function NavigationStateListenerProvider({
164
+ isSynced,
164
165
  state,
165
166
  getState,
166
167
  subscribe,
167
168
  children,
168
169
  }: {
170
+ isSynced: boolean;
169
171
  state: NavigationState<ParamListBase>;
170
172
  getState: () => NavigationState<ParamListBase>;
171
173
  subscribe: (callback: () => void) => () => void;
172
174
  children: React.ReactNode;
173
175
  }) {
174
- const snapshotRef = React.useRef(state);
175
-
176
- React.useInsertionEffect(() => {
177
- snapshotRef.current = state;
178
- }, [state]);
179
-
180
176
  const [listeners] = React.useState(
181
177
  () => new Set<(event: NavigationStateEvent) => void>()
182
178
  );
@@ -185,10 +181,22 @@ export function NavigationStateListenerProvider({
185
181
  listeners.forEach((listener) => listener('commit'));
186
182
  }, [listeners, state]);
187
183
 
184
+ const snapshotRef = React.useRef(state);
185
+
186
+ React.useInsertionEffect(() => {
187
+ snapshotRef.current = state;
188
+ }, [state]);
189
+
190
+ // Route config or nested params can produce new state during render.
191
+ // If the scheduled update hasn't synced that state to the store yet,
192
+ // We need to expose the rendered state so consumers match the navigator.
193
+ // We keep this `undefined` for other scenarios, so the context isn't recreated.
194
+ const nextState = isSynced ? undefined : state;
195
+
188
196
  const context = React.useMemo(
189
197
  () => ({
190
198
  getState,
191
- getSnapshot: () => snapshotRef.current,
199
+ getSnapshot: () => nextState ?? snapshotRef.current,
192
200
  subscribe: (callback: (event: NavigationStateEvent) => void) => {
193
201
  const unsubscribe = subscribe(() => callback('update'));
194
202
 
@@ -200,7 +208,7 @@ export function NavigationStateListenerProvider({
200
208
  };
201
209
  },
202
210
  }),
203
- [getState, listeners, subscribe]
211
+ [getState, listeners, nextState, subscribe]
204
212
  );
205
213
 
206
214
  return (
@@ -0,0 +1,29 @@
1
+ import type { ParamListBase } from '@react-navigation/routers';
2
+ import * as React from 'react';
3
+
4
+ import { NavigationBuilderContext } from './NavigationBuilderContext';
5
+ import { NavigationRouteContext } from './NavigationProvider';
6
+ import type { NavigationHelpers } from './types';
7
+
8
+ type Options = {
9
+ navigation: NavigationHelpers<ParamListBase>;
10
+ };
11
+
12
+ /**
13
+ * Hook which lets the container access the navigation object of the root navigator.
14
+ * It's used for the navigation object returned by `useNavigation` outside a screen.
15
+ */
16
+ export function useOnGetNavigation({ navigation }: Options) {
17
+ const { addKeyedListener } = React.use(NavigationBuilderContext);
18
+ const route = React.use(NavigationRouteContext);
19
+
20
+ React.useInsertionEffect(() => {
21
+ // We only register if this is the root navigator
22
+ // i.e. there is no parent route
23
+ if (route != null) {
24
+ return;
25
+ }
26
+
27
+ return addKeyedListener?.('getNavigation', 'root', () => navigation);
28
+ }, [addKeyedListener, navigation, route]);
29
+ }