@react-navigation/core 7.23.0 → 7.25.0

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 (93) hide show
  1. package/lib/module/BaseNavigationContainer.js +48 -21
  2. package/lib/module/BaseNavigationContainer.js.map +1 -1
  3. package/lib/module/NavigationBuilderContext.js +7 -11
  4. package/lib/module/NavigationBuilderContext.js.map +1 -1
  5. package/lib/module/NavigationIndependentTree.js +41 -10
  6. package/lib/module/NavigationIndependentTree.js.map +1 -1
  7. package/lib/module/NavigationProvider.js +2 -2
  8. package/lib/module/NavigationProvider.js.map +1 -1
  9. package/lib/module/NavigationStateContext.js.map +1 -1
  10. package/lib/module/PreventRemoveProvider.js +0 -1
  11. package/lib/module/PreventRemoveProvider.js.map +1 -1
  12. package/lib/module/SceneView.js +25 -18
  13. package/lib/module/SceneView.js.map +1 -1
  14. package/lib/module/StaticNavigation.js +53 -18
  15. package/lib/module/StaticNavigation.js.map +1 -1
  16. package/lib/module/types.js +7 -0
  17. package/lib/module/types.js.map +1 -1
  18. package/lib/module/useDescriptors.js +5 -5
  19. package/lib/module/useDescriptors.js.map +1 -1
  20. package/lib/module/useFocusedListenersChildrenAdapter.js +2 -2
  21. package/lib/module/useFocusedListenersChildrenAdapter.js.map +1 -1
  22. package/lib/module/useKeyedChildListeners.js +3 -1
  23. package/lib/module/useKeyedChildListeners.js.map +1 -1
  24. package/lib/module/useNavigation.js.map +1 -1
  25. package/lib/module/useNavigationBuilder.js +6 -4
  26. package/lib/module/useNavigationBuilder.js.map +1 -1
  27. package/lib/module/useNavigationCache.js +15 -27
  28. package/lib/module/useNavigationCache.js.map +1 -1
  29. package/lib/module/useNavigationHelpers.js +17 -7
  30. package/lib/module/useNavigationHelpers.js.map +1 -1
  31. package/lib/module/useNavigationState.js +1 -1
  32. package/lib/module/useNavigationState.js.map +1 -1
  33. package/lib/module/useOnAction.js +2 -2
  34. package/lib/module/useOnAction.js.map +1 -1
  35. package/lib/module/useOnGetState.js +2 -2
  36. package/lib/module/useOnGetState.js.map +1 -1
  37. package/lib/module/useOnPreventRemove.js +2 -2
  38. package/lib/module/useOnPreventRemove.js.map +1 -1
  39. package/lib/module/useOnRouteFocus.js +2 -2
  40. package/lib/module/useOnRouteFocus.js.map +1 -1
  41. package/lib/module/useOptionsGetters.js +4 -8
  42. package/lib/module/useOptionsGetters.js.map +1 -1
  43. package/lib/module/useScheduleUpdate.js +2 -2
  44. package/lib/module/useScheduleUpdate.js.map +1 -1
  45. package/lib/typescript/src/BaseNavigationContainer.d.ts.map +1 -1
  46. package/lib/typescript/src/NavigationBuilderContext.d.ts +11 -7
  47. package/lib/typescript/src/NavigationBuilderContext.d.ts.map +1 -1
  48. package/lib/typescript/src/NavigationIndependentTree.d.ts.map +1 -1
  49. package/lib/typescript/src/NavigationProvider.d.ts.map +1 -1
  50. package/lib/typescript/src/NavigationStateContext.d.ts +0 -1
  51. package/lib/typescript/src/NavigationStateContext.d.ts.map +1 -1
  52. package/lib/typescript/src/PreventRemoveProvider.d.ts.map +1 -1
  53. package/lib/typescript/src/SceneView.d.ts.map +1 -1
  54. package/lib/typescript/src/StaticNavigation.d.ts.map +1 -1
  55. package/lib/typescript/src/types.d.ts +49 -1
  56. package/lib/typescript/src/types.d.ts.map +1 -1
  57. package/lib/typescript/src/useDescriptors.d.ts.map +1 -1
  58. package/lib/typescript/src/useKeyedChildListeners.d.ts.map +1 -1
  59. package/lib/typescript/src/useNavigation.d.ts +2 -5
  60. package/lib/typescript/src/useNavigation.d.ts.map +1 -1
  61. package/lib/typescript/src/useNavigationBuilder.d.ts.map +1 -1
  62. package/lib/typescript/src/useNavigationCache.d.ts.map +1 -1
  63. package/lib/typescript/src/useNavigationHelpers.d.ts.map +1 -1
  64. package/lib/typescript/src/useNavigationState.d.ts +5 -0
  65. package/lib/typescript/src/useNavigationState.d.ts.map +1 -1
  66. package/lib/typescript/src/useOnRouteFocus.d.ts.map +1 -1
  67. package/lib/typescript/src/useOptionsGetters.d.ts +2 -0
  68. package/lib/typescript/src/useOptionsGetters.d.ts.map +1 -1
  69. package/lib/typescript/src/useScheduleUpdate.d.ts.map +1 -1
  70. package/package.json +14 -15
  71. package/src/BaseNavigationContainer.tsx +69 -31
  72. package/src/NavigationBuilderContext.tsx +27 -17
  73. package/src/NavigationIndependentTree.tsx +41 -13
  74. package/src/NavigationProvider.tsx +11 -8
  75. package/src/NavigationStateContext.tsx +0 -4
  76. package/src/PreventRemoveProvider.tsx +0 -1
  77. package/src/SceneView.tsx +22 -27
  78. package/src/StaticNavigation.tsx +71 -17
  79. package/src/types.tsx +76 -8
  80. package/src/useDescriptors.tsx +5 -4
  81. package/src/useFocusedListenersChildrenAdapter.tsx +2 -2
  82. package/src/useKeyedChildListeners.tsx +1 -0
  83. package/src/useNavigation.tsx +2 -5
  84. package/src/useNavigationBuilder.tsx +5 -6
  85. package/src/useNavigationCache.tsx +14 -31
  86. package/src/useNavigationHelpers.tsx +18 -6
  87. package/src/useNavigationState.tsx +1 -1
  88. package/src/useOnAction.tsx +2 -2
  89. package/src/useOnGetState.tsx +2 -2
  90. package/src/useOnPreventRemove.tsx +2 -2
  91. package/src/useOnRouteFocus.tsx +2 -4
  92. package/src/useOptionsGetters.tsx +9 -6
  93. package/src/useScheduleUpdate.tsx +2 -4
@@ -356,6 +356,9 @@ const MemoizedScreen = React.memo(
356
356
 
357
357
  MemoizedScreen.displayName = 'Memo(Screen)';
358
358
 
359
+ const hasPropertyGetter = (object: object, property: PropertyKey) =>
360
+ Object.getOwnPropertyDescriptor(object, property)?.get != null;
361
+
359
362
  const getItemsFromScreens = (
360
363
  Screen: React.ComponentType<any>,
361
364
  screens: StaticConfigScreens<any, any, any, any, any>
@@ -366,18 +369,57 @@ const getItemsFromScreens = (
366
369
  let useIf: (() => boolean) | undefined;
367
370
 
368
371
  let isNavigator = false;
372
+ let getComponent: (() => React.ComponentType<any>) | undefined;
369
373
 
370
374
  if ('screen' in item) {
371
- const { screen, if: _if, ...rest } = item;
372
-
373
- useIf = _if;
374
- props = rest;
375
+ if (hasPropertyGetter(item, 'screen')) {
376
+ const rest: Record<string, unknown> = {};
377
+
378
+ // Iterate over the keys to avoid triggering the getter for 'screen'
379
+ for (const key of Object.keys(item)) {
380
+ if (key === 'screen' || key === 'if') {
381
+ continue;
382
+ }
383
+
384
+ rest[key] = Reflect.get(item, key);
385
+ }
386
+
387
+ useIf = item.if;
388
+ props = rest;
389
+
390
+ getComponent = () => {
391
+ const screen = item.screen;
392
+
393
+ if (isValidElementType(screen)) {
394
+ return screen;
395
+ }
396
+
397
+ if (
398
+ screen != null &&
399
+ typeof screen === 'object' &&
400
+ 'config' in screen
401
+ ) {
402
+ throw new Error(
403
+ `Got a navigator object for the screen '${name}' instead of a component. A navigator cannot be specified when using a getter.`
404
+ );
405
+ }
375
406
 
376
- if (isValidElementType(screen)) {
377
- component = screen;
378
- } else if ('config' in screen) {
379
- isNavigator = true;
380
- component = screen.getComponent();
407
+ throw new Error(
408
+ `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.`
409
+ );
410
+ };
411
+ } else {
412
+ const { screen, if: _if, ...rest } = item;
413
+
414
+ useIf = _if;
415
+ props = rest;
416
+
417
+ if (isValidElementType(screen)) {
418
+ component = screen;
419
+ } else if ('config' in screen) {
420
+ isNavigator = true;
421
+ component = screen.getComponent();
422
+ }
381
423
  }
382
424
  } else if (isValidElementType(item)) {
383
425
  component = item;
@@ -386,18 +428,29 @@ const getItemsFromScreens = (
386
428
  component = item.getComponent();
387
429
  }
388
430
 
389
- if (component == null) {
431
+ let children: (() => React.JSX.Element) | undefined;
432
+
433
+ if (getComponent != null) {
434
+ let element: React.JSX.Element | undefined;
435
+
436
+ children = () =>
437
+ (element ??= <MemoizedScreen component={getComponent()} />);
438
+ } else if (component != null) {
439
+ const element = isNavigator ? (
440
+ React.createElement(component, {})
441
+ ) : (
442
+ <MemoizedScreen component={component} />
443
+ );
444
+
445
+ children = () => element;
446
+ }
447
+
448
+ if (children == null) {
390
449
  throw new Error(
391
450
  `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.`
392
451
  );
393
452
  }
394
453
 
395
- const element = isNavigator ? (
396
- React.createElement(component, {})
397
- ) : (
398
- <MemoizedScreen component={component} />
399
- );
400
-
401
454
  return () => {
402
455
  const shouldRender = useIf == null || useIf();
403
456
 
@@ -407,7 +460,7 @@ const getItemsFromScreens = (
407
460
 
408
461
  return (
409
462
  <Screen key={name} name={name} {...props}>
410
- {() => element}
463
+ {children}
411
464
  </Screen>
412
465
  );
413
466
  };
@@ -732,6 +785,7 @@ export function createPathConfigForStaticNavigation(
732
785
  !hasExplicitScreens &&
733
786
  !hasDisabledLinking &&
734
787
  'screen' in item &&
788
+ !hasPropertyGetter(item, 'screen') &&
735
789
  'config' in item.screen &&
736
790
  (item.screen.config.screens || item.screen.config.groups)
737
791
  ) {
package/src/types.tsx CHANGED
@@ -312,9 +312,8 @@ export type EventArg<
312
312
  export type EventListenerCallback<
313
313
  EventMap extends EventMapBase,
314
314
  EventName extends keyof EventMap,
315
- EventCanPreventDefault extends
316
- | boolean
317
- | undefined = EventMap[EventName]['canPreventDefault'],
315
+ EventCanPreventDefault extends boolean | undefined =
316
+ EventMap[EventName]['canPreventDefault'],
318
317
  > = (
319
318
  e: EventArg<
320
319
  EventName,
@@ -642,6 +641,57 @@ export type NavigationProp<
642
641
  EventConsumer<EventMap & EventMapCore<State>> &
643
642
  PrivateValueStore<[ParamList, RouteName, EventMap]>;
644
643
 
644
+ /**
645
+ * A stripped down NavigationProp if the navigator is not known.
646
+ *
647
+ * The hook can be used in `NavigationContainer` directly, not inside of a navigator.
648
+ * So navigator specific methods won't be available.
649
+ */
650
+ export type GenericNavigation<ParamList extends {}> = Omit<
651
+ NavigationProp<ParamList>,
652
+ 'getState' | 'setParams' | 'replaceParams' | 'setOptions'
653
+ > & {
654
+ /**
655
+ * Returns the navigator's state.
656
+ *
657
+ * This may return `undefined` if used outside of a navigator,
658
+ * as the navigator may not have rendered yet
659
+ */
660
+ getState(): NavigationState | undefined;
661
+
662
+ /**
663
+ * Update the param object for the route.
664
+ * The new params will be shallow merged with the old one.
665
+ *
666
+ * @param params Partial params object for the current route.
667
+ */
668
+ setParams(
669
+ // We don't know which route to set params for
670
+ params: unknown
671
+ ): void;
672
+
673
+ /**
674
+ * Replace the param object for the route
675
+ *
676
+ * @param params Params object for the current route.
677
+ */
678
+ replaceParams(
679
+ // We don't know which route to replace params for
680
+ params: unknown
681
+ ): void;
682
+
683
+ /**
684
+ * Update the options for the route.
685
+ * The options object will be shallow merged with default options object.
686
+ *
687
+ * @param options Partial options object for the current screen.
688
+ */
689
+ setOptions(
690
+ // We don't know which navigator to set options for
691
+ options: unknown
692
+ ): void;
693
+ };
694
+
645
695
  export type RouteProp<
646
696
  ParamList extends ParamListBase,
647
697
  RouteName extends keyof ParamList = Keyof<ParamList>,
@@ -1327,9 +1377,27 @@ export type PathConfig<ParamList extends {}> = Partial<PathConfigAlias> & {
1327
1377
  };
1328
1378
 
1329
1379
  export type PathConfigMap<ParamList extends {}> = {
1330
- [RouteName in keyof ParamList]?: NonNullable<
1331
- ParamList[RouteName]
1332
- > extends NavigatorScreenParams<infer T extends {}>
1333
- ? string | PathConfig<T>
1334
- : string | Omit<PathConfig<{}>, 'screens' | 'initialRouteName'>;
1380
+ [RouteName in keyof ParamList]?: PathConfigForParams<ParamList[RouteName]>;
1335
1381
  };
1382
+
1383
+ type NavigatorRoutes<Params> = Extract<Params, { screen: PropertyKey }>;
1384
+
1385
+ type PathConfigForParams<Params> =
1386
+ | string
1387
+ | (Omit<PathConfig<{}>, 'screens' | 'initialRouteName'> &
1388
+ ([NonNullable<Params>] extends [never]
1389
+ ? unknown
1390
+ : [NonNullable<Params>] extends [
1391
+ NavigatorScreenParams<Record<PropertyKey, unknown>>,
1392
+ ]
1393
+ ? {
1394
+ initialRouteName?: NavigatorRoutes<Params>['screen'];
1395
+ screens?: {
1396
+ [Route in NavigatorRoutes<Params> as Route['screen']]?: PathConfigForParams<
1397
+ Route extends { params?: infer ChildParams }
1398
+ ? ChildParams
1399
+ : never
1400
+ >;
1401
+ };
1402
+ }
1403
+ : unknown));
@@ -13,6 +13,7 @@ import {
13
13
  type AddKeyedListener,
14
14
  type AddListener,
15
15
  NavigationBuilderContext,
16
+ useNavigationBuilderContext,
16
17
  } from './NavigationBuilderContext';
17
18
  import { NavigationProvider } from './NavigationProvider';
18
19
  import { SceneView } from './SceneView';
@@ -149,9 +150,9 @@ export function useDescriptors<
149
150
  onOptionsChange,
150
151
  scheduleUpdate,
151
152
  flushUpdates,
152
- stackRef,
153
+ withStackTrace,
153
154
  getIsStateEmitted,
154
- } = React.useContext(NavigationBuilderContext);
155
+ } = useNavigationBuilderContext();
155
156
 
156
157
  const context = React.useMemo(
157
158
  () => ({
@@ -166,7 +167,7 @@ export function useDescriptors<
166
167
  getIsStateEmitted,
167
168
  scheduleUpdate,
168
169
  flushUpdates,
169
- stackRef,
170
+ withStackTrace,
170
171
  }),
171
172
  [
172
173
  navigation,
@@ -180,7 +181,7 @@ export function useDescriptors<
180
181
  getIsStateEmitted,
181
182
  scheduleUpdate,
182
183
  flushUpdates,
183
- stackRef,
184
+ withStackTrace,
184
185
  ]
185
186
  );
186
187
 
@@ -4,7 +4,7 @@ import * as React from 'react';
4
4
  import {
5
5
  type FocusedNavigationCallback,
6
6
  type FocusedNavigationListener,
7
- NavigationBuilderContext,
7
+ useNavigationBuilderContext,
8
8
  } from './NavigationBuilderContext';
9
9
  import type { NavigationHelpers } from './types';
10
10
 
@@ -20,7 +20,7 @@ export function useFocusedListenersChildrenAdapter({
20
20
  navigation,
21
21
  focusedListeners,
22
22
  }: Options) {
23
- const { addListener } = React.useContext(NavigationBuilderContext);
23
+ const { addListener } = useNavigationBuilderContext();
24
24
 
25
25
  const listener = React.useCallback(
26
26
  (callback: FocusedNavigationCallback<any>) => {
@@ -12,6 +12,7 @@ export function useKeyedChildListeners() {
12
12
  KeyedListenerMap[K] | undefined
13
13
  >;
14
14
  }>(
15
+ // eslint-disable-next-line @eslint-react/purity
15
16
  Object.assign(Object.create(null), {
16
17
  getState: {},
17
18
  beforeRemove: {},
@@ -1,9 +1,8 @@
1
- import { type NavigationState } from '@react-navigation/routers';
2
1
  import * as React from 'react';
3
2
 
4
3
  import { NavigationContainerRefContext } from './NavigationContainerRefContext';
5
4
  import { NavigationContext } from './NavigationContext';
6
- import type { NavigationProp } from './types';
5
+ import type { GenericNavigation } from './types';
7
6
 
8
7
  /**
9
8
  * Hook to access the navigation prop of the parent screen anywhere.
@@ -11,9 +10,7 @@ import type { NavigationProp } from './types';
11
10
  * @returns Navigation prop of the parent screen.
12
11
  */
13
12
  export function useNavigation<
14
- T = Omit<NavigationProp<ReactNavigation.RootParamList>, 'getState'> & {
15
- getState(): NavigationState | undefined;
16
- },
13
+ T = GenericNavigation<ReactNavigation.RootParamList>,
17
14
  >(): T {
18
15
  const root = React.useContext(NavigationContainerRefContext);
19
16
  const navigation = React.useContext(NavigationContext);
@@ -19,7 +19,7 @@ import { ConsumedParamsContext } from './ConsumedParamsContext';
19
19
  import { deepFreeze } from './deepFreeze';
20
20
  import { Group } from './Group';
21
21
  import { isArrayEqual } from './isArrayEqual';
22
- import { NavigationBuilderContext } from './NavigationBuilderContext';
22
+ import { useNavigationBuilderContext } from './NavigationBuilderContext';
23
23
  import { NavigationHelpersContext } from './NavigationHelpersContext';
24
24
  // eslint-disable-next-line import-x/no-deprecated
25
25
  import { NavigationMetaContext } from './NavigationMetaContext';
@@ -431,6 +431,7 @@ export function useNavigationBuilder<
431
431
  routeKeyList[name] = config.keys;
432
432
  routeParamList[name] = config.props.initialParams;
433
433
 
434
+ // eslint-disable-next-line @eslint-react/purity
434
435
  Object.assign(routeGetIdList, { [name]: config.props.getId });
435
436
  }
436
437
 
@@ -463,9 +464,7 @@ export function useNavigationBuilder<
463
464
  getIsInitial,
464
465
  } = React.useContext(NavigationStateContext);
465
466
 
466
- const { onEmitEvent, getIsStateEmitted } = React.useContext(
467
- NavigationBuilderContext
468
- );
467
+ const { onEmitEvent, getIsStateEmitted } = useNavigationBuilderContext();
469
468
 
470
469
  const stateCleanupRef = React.useRef<boolean>(false);
471
470
  const lastStateRef = React.useRef<State | PartialState<State> | undefined>(
@@ -609,7 +608,7 @@ export function useNavigationBuilder<
609
608
  // Instead, we handle changes to these in the nextState code below. Note
610
609
  // that some changes to routeConfigs are explicitly ignored, such as changes
611
610
  // to initialParams
612
- // eslint-disable-next-line react-hooks/exhaustive-deps
611
+ // eslint-disable-next-line @eslint-react/exhaustive-deps
613
612
  }, [currentState, router, isStateValid]);
614
613
 
615
614
  const previousRouteKeyListRef = React.useRef(routeKeyList);
@@ -854,7 +853,7 @@ export function useNavigationBuilder<
854
853
  // causing the initial state to be lost after cleanup wipes the container state.
855
854
  lastNotifiedStateRef.current = null;
856
855
  };
857
- // eslint-disable-next-line react-hooks/exhaustive-deps
856
+ // eslint-disable-next-line @eslint-react/exhaustive-deps
858
857
  }, []);
859
858
 
860
859
  const getState = useLatestCallback((): State => {
@@ -7,7 +7,7 @@ import {
7
7
  } from '@react-navigation/routers';
8
8
  import * as React from 'react';
9
9
 
10
- import { NavigationBuilderContext } from './NavigationBuilderContext';
10
+ import { useNavigationBuilderContext } from './NavigationBuilderContext';
11
11
  import type { NavigationHelpers, NavigationProp } from './types';
12
12
  import type { NavigationEventEmitter } from './useEventEmitter';
13
13
 
@@ -66,7 +66,7 @@ export function useNavigationCache<
66
66
  router,
67
67
  emitter,
68
68
  }: Options<State, ScreenOptions, EventMap>) {
69
- const { stackRef } = React.useContext(NavigationBuilderContext);
69
+ const { withStackTrace } = useNavigationBuilderContext();
70
70
 
71
71
  const base = React.useMemo((): NavigationItem<
72
72
  State,
@@ -130,7 +130,7 @@ export function useNavigationCache<
130
130
  // In reality, these deps will rarely change, if ever
131
131
  const cache = React.useMemo(
132
132
  () => ({ current: {} as NavigationCache<State, ScreenOptions, EventMap> }),
133
- // eslint-disable-next-line react-hooks/exhaustive-deps
133
+ // eslint-disable-next-line @eslint-react/exhaustive-deps
134
134
  [base, getState, navigation, setOptions, emitter]
135
135
  );
136
136
 
@@ -148,33 +148,14 @@ export function useNavigationCache<
148
148
  acc[route.key] = previous;
149
149
  } else {
150
150
  const dispatch = (thunk: Thunk) => {
151
- const action = typeof thunk === 'function' ? thunk(getState()) : thunk;
151
+ withStackTrace(dispatch, () => {
152
+ const action =
153
+ typeof thunk === 'function' ? thunk(getState()) : thunk;
152
154
 
153
- if (action != null) {
154
- navigation.dispatch({ source: route.key, ...action });
155
- }
156
- };
157
-
158
- const withStack = (callback: () => void) => {
159
- let isStackSet = false;
160
-
161
- try {
162
- if (
163
- process.env.NODE_ENV !== 'production' &&
164
- stackRef &&
165
- !stackRef.current
166
- ) {
167
- // Capture the stack trace for devtools
168
- stackRef.current = new Error().stack;
169
- isStackSet = true;
155
+ if (action != null) {
156
+ navigation.dispatch({ source: route.key, ...action });
170
157
  }
171
-
172
- callback();
173
- } finally {
174
- if (isStackSet && stackRef) {
175
- stackRef.current = undefined;
176
- }
177
- }
158
+ });
178
159
  };
179
160
 
180
161
  const actions = {
@@ -184,12 +165,14 @@ export function useNavigationCache<
184
165
 
185
166
  const helpers = Object.keys(actions).reduce<Record<string, () => void>>(
186
167
  (acc, name) => {
187
- acc[name] = (...args: any) =>
188
- withStack(() =>
168
+ const helper = (...args: any) =>
169
+ withStackTrace(helper, () =>
189
170
  // @ts-expect-error: name is a valid key, but TypeScript is dumb
190
171
  dispatch(actions[name](...args))
191
172
  );
192
173
 
174
+ acc[name] = helper;
175
+
193
176
  return acc;
194
177
  },
195
178
  {}
@@ -200,7 +183,7 @@ export function useNavigationCache<
200
183
  ...helpers,
201
184
  // FIXME: too much work to fix the types for now
202
185
  ...(emitter.create(route.key) as any),
203
- dispatch: (thunk: Thunk) => withStack(() => dispatch(thunk)),
186
+ dispatch,
204
187
  getParent: (id?: string) => {
205
188
  if (id !== undefined && id === base.getId()) {
206
189
  // If the passed id is the same as the current navigation id,
@@ -7,6 +7,7 @@ import {
7
7
  } from '@react-navigation/routers';
8
8
  import * as React from 'react';
9
9
 
10
+ import { useNavigationBuilderContext } from './NavigationBuilderContext';
10
11
  import { NavigationContext } from './NavigationContext';
11
12
  import { type NavigationHelpers, PrivateValueStore } from './types';
12
13
  import type { NavigationEventEmitter } from './useEventEmitter';
@@ -44,6 +45,7 @@ export function useNavigationHelpers<
44
45
  router,
45
46
  }: Options<State, Action>) {
46
47
  const parentNavigationHelpers = React.useContext(NavigationContext);
48
+ const { withStackTrace } = useNavigationBuilderContext();
47
49
 
48
50
  // In some cases (e.g. route names change), internal state might have changed
49
51
  // But it hasn't been committed yet, so hasn't propagated to any listeners
@@ -64,13 +66,15 @@ export function useNavigationHelpers<
64
66
 
65
67
  return React.useMemo(() => {
66
68
  const dispatch = (op: Action | ((state: State) => Action)) => {
67
- const action = typeof op === 'function' ? op(getState()) : op;
69
+ withStackTrace(dispatch, () => {
70
+ const action = typeof op === 'function' ? op(getState()) : op;
68
71
 
69
- const handled = onAction(action);
72
+ const handled = onAction(action);
70
73
 
71
- if (!handled) {
72
- onUnhandledAction?.(action);
73
- }
74
+ if (!handled) {
75
+ onUnhandledAction?.(action);
76
+ }
77
+ });
74
78
  };
75
79
 
76
80
  const actions = {
@@ -79,8 +83,15 @@ export function useNavigationHelpers<
79
83
  };
80
84
 
81
85
  const helpers = Object.keys(actions).reduce((acc, name) => {
86
+ const helper = (...args: any) =>
87
+ withStackTrace(helper, () =>
88
+ // @ts-expect-error: name is a valid key, but TypeScript is dumb
89
+ dispatch(actions[name](...args))
90
+ );
91
+
82
92
  // @ts-expect-error: name is a valid key, but TypeScript is dumb
83
- acc[name] = (...args: any) => dispatch(actions[name](...args));
93
+ acc[name] = helper;
94
+
84
95
  return acc;
85
96
  }, {} as ActionHelpers);
86
97
 
@@ -143,5 +154,6 @@ export function useNavigationHelpers<
143
154
  onUnhandledAction,
144
155
  navigatorId,
145
156
  stateRef,
157
+ withStackTrace,
146
158
  ]);
147
159
  }
@@ -108,6 +108,6 @@ export function NavigationStateListenerProvider({
108
108
  );
109
109
  }
110
110
 
111
- const NavigationStateListenerContext = React.createContext<
111
+ export const NavigationStateListenerContext = React.createContext<
112
112
  NavigationStateListener | undefined
113
113
  >(undefined);
@@ -11,7 +11,7 @@ import { DeprecatedNavigationInChildContext } from './DeprecatedNavigationInChil
11
11
  import {
12
12
  type ChildActionListener,
13
13
  type ChildBeforeRemoveListener,
14
- NavigationBuilderContext,
14
+ useNavigationBuilderContext,
15
15
  } from './NavigationBuilderContext';
16
16
  import type { EventMapCore } from './types';
17
17
  import type { NavigationEventEmitter } from './useEventEmitter';
@@ -53,7 +53,7 @@ export function useOnAction<State extends NavigationState>({
53
53
  addListener: addListenerParent,
54
54
  onDispatchAction,
55
55
  flushUpdates,
56
- } = React.useContext(NavigationBuilderContext);
56
+ } = useNavigationBuilderContext();
57
57
  const navigationInChildEnabled = React.useContext(
58
58
  DeprecatedNavigationInChildContext
59
59
  );
@@ -4,7 +4,7 @@ import * as React from 'react';
4
4
  import { isArrayEqual } from './isArrayEqual';
5
5
  import {
6
6
  type GetStateListener,
7
- NavigationBuilderContext,
7
+ useNavigationBuilderContext,
8
8
  } from './NavigationBuilderContext';
9
9
  import { NavigationRouteContext } from './NavigationProvider';
10
10
 
@@ -14,7 +14,7 @@ type Options = {
14
14
  };
15
15
 
16
16
  export function useOnGetState({ getState, getStateListeners }: Options) {
17
- const { addKeyedListener } = React.useContext(NavigationBuilderContext);
17
+ const { addKeyedListener } = useNavigationBuilderContext();
18
18
  const route = React.useContext(NavigationRouteContext);
19
19
  const key = route ? route.key : 'root';
20
20
 
@@ -9,7 +9,7 @@ import * as React from 'react';
9
9
 
10
10
  import {
11
11
  type ChildBeforeRemoveListener,
12
- NavigationBuilderContext,
12
+ useNavigationBuilderContext,
13
13
  } from './NavigationBuilderContext';
14
14
  import { NavigationRouteContext } from './NavigationProvider';
15
15
  import type { EventMapCore } from './types';
@@ -107,7 +107,7 @@ export function useOnPreventRemove({
107
107
  emitter,
108
108
  beforeRemoveListeners,
109
109
  }: Options) {
110
- const { addKeyedListener } = React.useContext(NavigationBuilderContext);
110
+ const { addKeyedListener } = useNavigationBuilderContext();
111
111
  const route = React.useContext(NavigationRouteContext);
112
112
  const routeKey = route?.key;
113
113
 
@@ -5,7 +5,7 @@ import type {
5
5
  } from '@react-navigation/routers';
6
6
  import * as React from 'react';
7
7
 
8
- import { NavigationBuilderContext } from './NavigationBuilderContext';
8
+ import { useNavigationBuilderContext } from './NavigationBuilderContext';
9
9
 
10
10
  type Options<State extends NavigationState, Action extends NavigationAction> = {
11
11
  router: Router<State, Action>;
@@ -23,9 +23,7 @@ export function useOnRouteFocus<
23
23
  State extends NavigationState,
24
24
  Action extends NavigationAction,
25
25
  >({ router, getState, key: sourceRouteKey, setState }: Options<State, Action>) {
26
- const { onRouteFocus: onRouteFocusParent } = React.useContext(
27
- NavigationBuilderContext
28
- );
26
+ const { onRouteFocus: onRouteFocusParent } = useNavigationBuilderContext();
29
27
 
30
28
  return React.useCallback(
31
29
  (key: string) => {
@@ -2,9 +2,12 @@ import type { ParamListBase } from '@react-navigation/routers';
2
2
  import * as React from 'react';
3
3
 
4
4
  import { NavigationBuilderContext } from './NavigationBuilderContext';
5
- import { NavigationStateContext } from './NavigationStateContext';
6
5
  import type { NavigationProp } from './types';
7
6
 
7
+ export const OptionsGetterContext = React.createContext<
8
+ ((key: string, getter: () => object | undefined | null) => void) | undefined
9
+ >(undefined);
10
+
8
11
  type Options = {
9
12
  key?: string;
10
13
  navigation?: NavigationProp<ParamListBase>;
@@ -17,17 +20,17 @@ export function useOptionsGetters({ key, options, navigation }: Options) {
17
20
  Record<string, () => object | undefined | null>
18
21
  >({});
19
22
 
20
- const { onOptionsChange } = React.useContext(NavigationBuilderContext);
21
- const { addOptionsGetter: parentAddOptionsGetter } = React.useContext(
22
- NavigationStateContext
23
- );
23
+ const onOptionsChange = React.useContext(
24
+ NavigationBuilderContext
25
+ )?.onOptionsChange;
26
+ const parentAddOptionsGetter = React.useContext(OptionsGetterContext);
24
27
 
25
28
  const optionsChangeListener = React.useCallback(() => {
26
29
  const isFocused = navigation?.isFocused() ?? true;
27
30
  const hasChildren = Object.keys(optionsGettersFromChildRef.current).length;
28
31
 
29
32
  if (isFocused && !hasChildren) {
30
- onOptionsChange(optionsRef.current ?? {});
33
+ onOptionsChange?.(optionsRef.current ?? {});
31
34
  }
32
35
  }, [navigation, onOptionsChange]);
33
36
 
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
2
 
3
- import { NavigationBuilderContext } from './NavigationBuilderContext';
3
+ import { useNavigationBuilderContext } from './NavigationBuilderContext';
4
4
  import { useClientLayoutEffect } from './useClientLayoutEffect';
5
5
 
6
6
  /**
@@ -12,9 +12,7 @@ import { useClientLayoutEffect } from './useClientLayoutEffect';
12
12
  * This lets nested updates be applied from the root down without clobbering each other.
13
13
  */
14
14
  export function useScheduleUpdate(callback: () => void) {
15
- const { scheduleUpdate, flushUpdates } = React.useContext(
16
- NavigationBuilderContext
17
- );
15
+ const { scheduleUpdate, flushUpdates } = useNavigationBuilderContext();
18
16
 
19
17
  React.useInsertionEffect(() => {
20
18
  scheduleUpdate(callback);