@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.
- package/lib/module/BaseNavigationContainer.js +128 -80
- package/lib/module/BaseNavigationContainer.js.map +1 -1
- package/lib/module/DataLoading.js +3 -1
- package/lib/module/DataLoading.js.map +1 -1
- package/lib/module/NavigationBuilderContext.js +2 -1
- package/lib/module/NavigationBuilderContext.js.map +1 -1
- package/lib/module/NavigationIndependentTree.js +13 -6
- package/lib/module/NavigationIndependentTree.js.map +1 -1
- package/lib/module/NavigationProvider.js +10 -1
- package/lib/module/NavigationProvider.js.map +1 -1
- package/lib/module/NavigationRootContext.js +8 -0
- package/lib/module/NavigationRootContext.js.map +1 -0
- package/lib/module/StaticNavigation.js +54 -19
- package/lib/module/StaticNavigation.js.map +1 -1
- package/lib/module/createNavigationContainerRef.js +1 -1
- package/lib/module/createNavigationContainerRef.js.map +1 -1
- package/lib/module/index.js +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/internal.js +2 -0
- package/lib/module/internal.js.map +1 -0
- package/lib/module/types.js +0 -1
- package/lib/module/types.js.map +1 -1
- package/lib/module/useDescriptors.js +4 -4
- package/lib/module/useDescriptors.js.map +1 -1
- package/lib/module/useKeyedChildListeners.js +1 -0
- package/lib/module/useKeyedChildListeners.js.map +1 -1
- package/lib/module/useNavigation.js +2 -2
- package/lib/module/useNavigation.js.map +1 -1
- package/lib/module/useNavigationBuilder.js +11 -4
- package/lib/module/useNavigationBuilder.js.map +1 -1
- package/lib/module/useNavigationCache.js +23 -31
- package/lib/module/useNavigationCache.js.map +1 -1
- package/lib/module/useNavigationHelpers.js +18 -37
- package/lib/module/useNavigationHelpers.js.map +1 -1
- package/lib/module/useNavigationState.js +13 -6
- package/lib/module/useNavigationState.js.map +1 -1
- package/lib/module/useOnGetNavigation.js +26 -0
- package/lib/module/useOnGetNavigation.js.map +1 -0
- package/lib/typescript/src/BaseNavigationContainer.d.ts.map +1 -1
- package/lib/typescript/src/DataLoading.d.ts.map +1 -1
- package/lib/typescript/src/NavigationBuilderContext.d.ts +4 -1
- package/lib/typescript/src/NavigationBuilderContext.d.ts.map +1 -1
- package/lib/typescript/src/NavigationIndependentTree.d.ts.map +1 -1
- package/lib/typescript/src/NavigationProvider.d.ts +9 -5
- package/lib/typescript/src/NavigationProvider.d.ts.map +1 -1
- package/lib/typescript/src/NavigationRootContext.d.ts +8 -0
- package/lib/typescript/src/NavigationRootContext.d.ts.map +1 -0
- package/lib/typescript/src/StaticNavigation.d.ts.map +1 -1
- package/lib/typescript/src/createNavigationContainerRef.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/internal.d.ts +3 -0
- package/lib/typescript/src/internal.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +3 -6
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/useKeyedChildListeners.d.ts +1 -0
- package/lib/typescript/src/useKeyedChildListeners.d.ts.map +1 -1
- package/lib/typescript/src/useNavigationBuilder.d.ts.map +1 -1
- package/lib/typescript/src/useNavigationCache.d.ts.map +1 -1
- package/lib/typescript/src/useNavigationHelpers.d.ts +1 -2
- package/lib/typescript/src/useNavigationHelpers.d.ts.map +1 -1
- package/lib/typescript/src/useNavigationState.d.ts +2 -1
- package/lib/typescript/src/useNavigationState.d.ts.map +1 -1
- package/lib/typescript/src/useOnGetNavigation.d.ts +12 -0
- package/lib/typescript/src/useOnGetNavigation.d.ts.map +1 -0
- package/package.json +9 -3
- package/src/BaseNavigationContainer.tsx +181 -94
- package/src/DataLoading.tsx +4 -1
- package/src/NavigationBuilderContext.tsx +10 -1
- package/src/NavigationIndependentTree.tsx +13 -7
- package/src/NavigationProvider.tsx +13 -6
- package/src/NavigationRootContext.tsx +11 -0
- package/src/StaticNavigation.tsx +77 -19
- package/src/createNavigationContainerRef.tsx +0 -2
- package/src/index.tsx +1 -0
- package/src/internal.tsx +2 -0
- package/src/types.tsx +3 -7
- package/src/useDescriptors.tsx +3 -3
- package/src/useKeyedChildListeners.tsx +1 -0
- package/src/useNavigation.tsx +2 -2
- package/src/useNavigationBuilder.tsx +11 -4
- package/src/useNavigationCache.tsx +24 -33
- package/src/useNavigationHelpers.tsx +19 -38
- package/src/useNavigationState.tsx +16 -8
- package/src/useOnGetNavigation.tsx +29 -0
- package/lib/module/checkSerializable.js +0 -51
- package/lib/module/checkSerializable.js.map +0 -1
- package/lib/typescript/src/checkSerializable.d.ts +0 -14
- package/lib/typescript/src/checkSerializable.d.ts.map +0 -1
- 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);
|
package/src/StaticNavigation.tsx
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
696
|
-
|
|
712
|
+
getComponent = () => {
|
|
713
|
+
const screen = item.screen;
|
|
697
714
|
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
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
|
-
|
|
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
|
-
{
|
|
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,
|
|
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
|
-
|
|
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' &&
|
|
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
|
) {
|
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,
|
package/src/internal.tsx
ADDED
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
|
-
|
|
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 {}> =
|
package/src/useDescriptors.tsx
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
159
|
+
withStackTrace,
|
|
160
160
|
]
|
|
161
161
|
);
|
|
162
162
|
|
package/src/useNavigation.tsx
CHANGED
|
@@ -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(
|
|
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={
|
|
1071
|
+
getState={getState}
|
|
1069
1072
|
subscribe={subscribe}
|
|
1070
1073
|
>
|
|
1071
1074
|
<FocusedRouteKeyContext.Provider value={focusedRoute.key}>
|
|
1072
|
-
<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
|
|
76
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
48
|
+
withStackTrace(dispatch, () => {
|
|
49
|
+
const action = typeof op === 'function' ? op(getState()) : op;
|
|
66
50
|
|
|
67
|
-
|
|
51
|
+
const handled = onAction(action);
|
|
68
52
|
|
|
69
|
-
|
|
70
|
-
|
|
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] =
|
|
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
|
|
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
|
-
|
|
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
|
+
}
|