@react-navigation/core 8.0.0-alpha.33 → 8.0.0-alpha.35
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/README.md +17 -14
- package/lib/module/BaseNavigationContainer.js +14 -2
- package/lib/module/BaseNavigationContainer.js.map +1 -1
- package/lib/module/NavigationBuilderContext.js +8 -13
- package/lib/module/NavigationBuilderContext.js.map +1 -1
- package/lib/module/NavigationIndependentTree.js +45 -15
- package/lib/module/NavigationIndependentTree.js.map +1 -1
- package/lib/module/NavigationProvider.js +9 -4
- package/lib/module/NavigationProvider.js.map +1 -1
- package/lib/module/PreventRemoveProvider.js +2 -2
- package/lib/module/PreventRemoveProvider.js.map +1 -1
- package/lib/module/SceneView.js +2 -1
- package/lib/module/SceneView.js.map +1 -1
- package/lib/module/getPathFromState.js +41 -16
- package/lib/module/getPathFromState.js.map +1 -1
- package/lib/module/getPatternParts.js +43 -12
- package/lib/module/getPatternParts.js.map +1 -1
- package/lib/module/getStateFromPath.js +175 -109
- package/lib/module/getStateFromPath.js.map +1 -1
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/useDescriptors.js +9 -6
- package/lib/module/useDescriptors.js.map +1 -1
- package/lib/module/useFocusEffect.js +4 -2
- package/lib/module/useFocusEffect.js.map +1 -1
- package/lib/module/useFocusEvents.js +4 -2
- package/lib/module/useFocusEvents.js.map +1 -1
- package/lib/module/useFocusedListenersChildrenAdapter.js +2 -2
- package/lib/module/useFocusedListenersChildrenAdapter.js.map +1 -1
- package/lib/module/useIsFocused.js +1 -0
- package/lib/module/useIsFocused.js.map +1 -1
- package/lib/module/useNavigationBuilder.js +42 -39
- package/lib/module/useNavigationBuilder.js.map +1 -1
- package/lib/module/useNavigationCache.js +85 -81
- package/lib/module/useNavigationCache.js.map +1 -1
- package/lib/module/useNavigationHelpers.js +2 -2
- package/lib/module/useNavigationHelpers.js.map +1 -1
- package/lib/module/useNavigationState.js +48 -23
- package/lib/module/useNavigationState.js.map +1 -1
- package/lib/module/useOnAction.js +6 -4
- package/lib/module/useOnAction.js.map +1 -1
- package/lib/module/useOnGetNavigation.js +2 -2
- package/lib/module/useOnGetNavigation.js.map +1 -1
- package/lib/module/useOnGetState.js +2 -2
- package/lib/module/useOnGetState.js.map +1 -1
- package/lib/module/useOnPreventRemove.js +2 -2
- package/lib/module/useOnPreventRemove.js.map +1 -1
- package/lib/module/useOnRouteFocus.js +2 -2
- package/lib/module/useOnRouteFocus.js.map +1 -1
- package/lib/module/useOptionsGetters.js +19 -20
- package/lib/module/useOptionsGetters.js.map +1 -1
- package/lib/module/usePreventRemoveContext.js +1 -1
- package/lib/module/usePreventRemoveContext.js.map +1 -1
- package/lib/module/useRouteCache.js +43 -33
- package/lib/module/useRouteCache.js.map +1 -1
- package/lib/module/useScheduleUpdate.js +5 -3
- package/lib/module/useScheduleUpdate.js.map +1 -1
- package/lib/module/useSyncState.js +9 -10
- package/lib/module/useSyncState.js.map +1 -1
- package/lib/typescript/src/BaseNavigationContainer.d.ts.map +1 -1
- package/lib/typescript/src/NavigationBuilderContext.d.ts +9 -6
- 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.map +1 -1
- package/lib/typescript/src/PreventRemoveProvider.d.ts.map +1 -1
- package/lib/typescript/src/SceneView.d.ts +2 -1
- package/lib/typescript/src/SceneView.d.ts.map +1 -1
- package/lib/typescript/src/getPathFromState.d.ts.map +1 -1
- package/lib/typescript/src/getPatternParts.d.ts +4 -2
- package/lib/typescript/src/getPatternParts.d.ts.map +1 -1
- package/lib/typescript/src/getStateFromPath.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +1 -1
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/useDescriptors.d.ts +2 -2
- package/lib/typescript/src/useDescriptors.d.ts.map +1 -1
- package/lib/typescript/src/useFocusEffect.d.ts.map +1 -1
- package/lib/typescript/src/useFocusEvents.d.ts.map +1 -1
- package/lib/typescript/src/useIsFocused.d.ts +1 -0
- package/lib/typescript/src/useIsFocused.d.ts.map +1 -1
- package/lib/typescript/src/useNavigationBuilder.d.ts +1 -3
- package/lib/typescript/src/useNavigationBuilder.d.ts.map +1 -1
- package/lib/typescript/src/useNavigationCache.d.ts.map +1 -1
- package/lib/typescript/src/useNavigationState.d.ts +1 -0
- package/lib/typescript/src/useNavigationState.d.ts.map +1 -1
- package/lib/typescript/src/useOnAction.d.ts.map +1 -1
- package/lib/typescript/src/useOnGetNavigation.d.ts.map +1 -1
- package/lib/typescript/src/useOnGetState.d.ts.map +1 -1
- package/lib/typescript/src/useOnPreventRemove.d.ts.map +1 -1
- package/lib/typescript/src/useOnRouteFocus.d.ts.map +1 -1
- package/lib/typescript/src/useOptionsGetters.d.ts +2 -4
- package/lib/typescript/src/useOptionsGetters.d.ts.map +1 -1
- package/lib/typescript/src/useRouteCache.d.ts +7 -12
- package/lib/typescript/src/useRouteCache.d.ts.map +1 -1
- package/lib/typescript/src/useScheduleUpdate.d.ts.map +1 -1
- package/lib/typescript/src/useSyncState.d.ts.map +1 -1
- package/lib/typescript/src/utilities.d.ts +3 -0
- package/lib/typescript/src/utilities.d.ts.map +1 -1
- package/package.json +2 -3
- package/src/BaseNavigationContainer.tsx +19 -2
- package/src/NavigationBuilderContext.tsx +22 -18
- package/src/NavigationIndependentTree.tsx +56 -22
- package/src/NavigationProvider.tsx +13 -4
- package/src/PreventRemoveProvider.tsx +2 -8
- package/src/SceneView.tsx +3 -1
- package/src/getPathFromState.tsx +63 -15
- package/src/getPatternParts.tsx +75 -14
- package/src/getStateFromPath.tsx +250 -113
- package/src/index.tsx +1 -0
- package/src/types.tsx +1 -1
- package/src/useDescriptors.tsx +20 -6
- package/src/useFocusEffect.tsx +5 -2
- package/src/useFocusEvents.tsx +5 -2
- package/src/useFocusedListenersChildrenAdapter.tsx +2 -2
- package/src/useIsFocused.tsx +4 -0
- package/src/useNavigationBuilder.tsx +54 -48
- package/src/useNavigationCache.tsx +130 -108
- package/src/useNavigationHelpers.tsx +2 -2
- package/src/useNavigationState.tsx +60 -29
- package/src/useOnAction.tsx +6 -5
- package/src/useOnGetNavigation.tsx +3 -2
- package/src/useOnGetState.tsx +4 -2
- package/src/useOnPreventRemove.tsx +4 -2
- package/src/useOnRouteFocus.tsx +2 -4
- package/src/useOptionsGetters.tsx +26 -25
- package/src/usePreventRemoveContext.tsx +1 -1
- package/src/useRouteCache.tsx +49 -33
- package/src/useScheduleUpdate.tsx +6 -3
- package/src/useSyncState.tsx +9 -11
- package/src/utilities.tsx +5 -2
- package/lib/module/useComponent.js +0 -34
- package/lib/module/useComponent.js.map +0 -1
- package/lib/typescript/src/useComponent.d.ts +0 -7
- package/lib/typescript/src/useComponent.d.ts.map +0 -1
- package/src/useComponent.tsx +0 -37
package/src/useFocusEffect.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
|
|
3
|
+
import { IsFocusedGetterContext } from './useIsFocused';
|
|
3
4
|
import { useNavigation } from './useNavigation';
|
|
4
5
|
|
|
5
6
|
type EffectCallback = () => undefined | void | (() => void);
|
|
@@ -14,6 +15,8 @@ type EffectCallback = () => undefined | void | (() => void);
|
|
|
14
15
|
export function useFocusEffect(effect: EffectCallback) {
|
|
15
16
|
const navigation = useNavigation();
|
|
16
17
|
|
|
18
|
+
const getIsFocused = React.use(IsFocusedGetterContext);
|
|
19
|
+
|
|
17
20
|
// eslint-disable-next-line prefer-rest-params
|
|
18
21
|
if (arguments[1] !== undefined) {
|
|
19
22
|
const message =
|
|
@@ -72,7 +75,7 @@ export function useFocusEffect(effect: EffectCallback) {
|
|
|
72
75
|
};
|
|
73
76
|
|
|
74
77
|
// We need to run the effect on initial render/dep changes if the screen is focused
|
|
75
|
-
if (
|
|
78
|
+
if (getIsFocused?.() ?? true) {
|
|
76
79
|
cleanup = callback();
|
|
77
80
|
isFocused = true;
|
|
78
81
|
}
|
|
@@ -109,5 +112,5 @@ export function useFocusEffect(effect: EffectCallback) {
|
|
|
109
112
|
unsubscribeFocus();
|
|
110
113
|
unsubscribeBlur();
|
|
111
114
|
};
|
|
112
|
-
}, [effect, navigation]);
|
|
115
|
+
}, [effect, navigation, getIsFocused]);
|
|
113
116
|
}
|
package/src/useFocusEvents.tsx
CHANGED
|
@@ -4,6 +4,7 @@ import * as React from 'react';
|
|
|
4
4
|
import { NavigationContext } from './NavigationProvider';
|
|
5
5
|
import type { EventMapCore } from './types';
|
|
6
6
|
import type { NavigationEventEmitter } from './useEventEmitter';
|
|
7
|
+
import { IsFocusedGetterContext } from './useIsFocused';
|
|
7
8
|
|
|
8
9
|
type Options<State extends NavigationState> = {
|
|
9
10
|
state: State;
|
|
@@ -18,6 +19,8 @@ export function useFocusEvents<State extends NavigationState>({
|
|
|
18
19
|
emitter,
|
|
19
20
|
}: Options<State>) {
|
|
20
21
|
const navigation = React.use(NavigationContext);
|
|
22
|
+
const getIsFocused = React.use(IsFocusedGetterContext);
|
|
23
|
+
|
|
21
24
|
const lastFocusedKeyRef = React.useRef<string | undefined>(undefined);
|
|
22
25
|
|
|
23
26
|
const currentFocusedRoute = state.routes[state.index];
|
|
@@ -51,7 +54,7 @@ export function useFocusEvents<State extends NavigationState>({
|
|
|
51
54
|
React.useEffect(() => {
|
|
52
55
|
const lastFocusedKey = lastFocusedKeyRef.current;
|
|
53
56
|
|
|
54
|
-
const isFocused =
|
|
57
|
+
const isFocused = getIsFocused?.() ?? true;
|
|
55
58
|
|
|
56
59
|
if (isFocused) {
|
|
57
60
|
// Remember the current route as focused if the navigator is focused
|
|
@@ -77,5 +80,5 @@ export function useFocusEvents<State extends NavigationState>({
|
|
|
77
80
|
|
|
78
81
|
emitter.emit({ type: 'blur', target: lastFocusedKey });
|
|
79
82
|
emitter.emit({ type: 'focus', target: currentFocusedKey });
|
|
80
|
-
}, [currentFocusedKey, emitter, navigation]);
|
|
83
|
+
}, [currentFocusedKey, emitter, navigation, getIsFocused]);
|
|
81
84
|
}
|
|
@@ -4,7 +4,7 @@ import * as React from 'react';
|
|
|
4
4
|
import {
|
|
5
5
|
type FocusedNavigationCallback,
|
|
6
6
|
type FocusedNavigationListener,
|
|
7
|
-
|
|
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 } =
|
|
23
|
+
const { addListener } = useNavigationBuilderContext();
|
|
24
24
|
|
|
25
25
|
const listener = React.useCallback(
|
|
26
26
|
(callback: FocusedNavigationCallback<any>) => {
|
package/src/useIsFocused.tsx
CHANGED
|
@@ -8,6 +8,10 @@ export const IsFocusedContext = React.createContext<boolean | undefined>(
|
|
|
8
8
|
undefined
|
|
9
9
|
);
|
|
10
10
|
|
|
11
|
+
export const IsFocusedGetterContext = React.createContext<
|
|
12
|
+
(() => boolean) | undefined
|
|
13
|
+
>(undefined);
|
|
14
|
+
|
|
11
15
|
/**
|
|
12
16
|
* Hook to get the current focus state of the screen. Returns a `true` if screen is focused, otherwise `false`.
|
|
13
17
|
* This can be used if a component needs to render something based on the focus state.
|
|
@@ -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 {
|
|
22
|
+
import { useNavigationBuilderContext } from './NavigationBuilderContext';
|
|
23
23
|
import { NavigationHelpersContext } from './NavigationHelpersContext';
|
|
24
24
|
import { NavigationMetaContext } from './NavigationMetaContext';
|
|
25
25
|
import { IsScreenContext, NavigationRouteContext } from './NavigationProvider';
|
|
@@ -36,7 +36,6 @@ import {
|
|
|
36
36
|
} from './types';
|
|
37
37
|
import { UnhandledActionContext } from './UnhandledActionContext';
|
|
38
38
|
import { useChildListeners } from './useChildListeners';
|
|
39
|
-
import { useComponent } from './useComponent';
|
|
40
39
|
import { type ScreenConfigWithParent, useDescriptors } from './useDescriptors';
|
|
41
40
|
import { useEventEmitter } from './useEventEmitter';
|
|
42
41
|
import { useFocusedListenersChildrenAdapter } from './useFocusedListenersChildrenAdapter';
|
|
@@ -460,20 +459,22 @@ export function useNavigationBuilder<
|
|
|
460
459
|
getIsInitial,
|
|
461
460
|
} = React.use(NavigationStateContext);
|
|
462
461
|
|
|
463
|
-
const { onEmitEvent, getIsStateEmitted } =
|
|
464
|
-
|
|
462
|
+
const { onEmitEvent, getIsStateEmitted } = useNavigationBuilderContext();
|
|
463
|
+
|
|
464
|
+
const mountStateRef = React.useRef<'initial' | 'mounted' | 'unmounted'>(
|
|
465
|
+
'initial'
|
|
465
466
|
);
|
|
466
467
|
|
|
467
|
-
const
|
|
468
|
-
const
|
|
468
|
+
const previousStateRef = React.useRef(currentState);
|
|
469
|
+
const savedStateRef = React.useRef<State | PartialState<State> | undefined>(
|
|
469
470
|
undefined
|
|
470
471
|
);
|
|
471
472
|
|
|
472
473
|
const setState = useLatestCallback(
|
|
473
474
|
(state: State | PartialState<State> | undefined) => {
|
|
474
|
-
if (
|
|
475
|
+
if (mountStateRef.current === 'unmounted') {
|
|
475
476
|
// Store the state locally in case the current navigator is in `Activity`
|
|
476
|
-
|
|
477
|
+
savedStateRef.current = state;
|
|
477
478
|
|
|
478
479
|
// State might have been already cleaned up due to unmount
|
|
479
480
|
// We don't want to update `route.state` in parent
|
|
@@ -485,6 +486,19 @@ export function useNavigationBuilder<
|
|
|
485
486
|
}
|
|
486
487
|
);
|
|
487
488
|
|
|
489
|
+
const lastState =
|
|
490
|
+
(mountStateRef.current !== 'mounted' ||
|
|
491
|
+
!isStateInitialized(currentState)) &&
|
|
492
|
+
// If the state object differs from previously committed state, we have new state
|
|
493
|
+
// e.g. because of a reset
|
|
494
|
+
// So we need to use it before checking if we should reuse the saved state.
|
|
495
|
+
(currentState === previousStateRef.current ||
|
|
496
|
+
(mountStateRef.current === 'unmounted' && currentState === undefined))
|
|
497
|
+
? // If the navigator is hidden with `Activity`, its state gets cleaned up due to effect cleanup.
|
|
498
|
+
// So we reuse the saved state to avoid remounting screens when the navigator is shown again.
|
|
499
|
+
savedStateRef.current
|
|
500
|
+
: undefined;
|
|
501
|
+
|
|
488
502
|
const [
|
|
489
503
|
stateBeforeInitialization,
|
|
490
504
|
initializedState,
|
|
@@ -496,17 +510,10 @@ export function useNavigationBuilder<
|
|
|
496
510
|
boolean,
|
|
497
511
|
object | undefined,
|
|
498
512
|
] => {
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
stateCleanupRef.current &&
|
|
504
|
-
lastStateRef.current &&
|
|
505
|
-
isStateValid(lastStateRef.current)
|
|
506
|
-
) {
|
|
507
|
-
const state: State = isStateInitialized(lastStateRef.current)
|
|
508
|
-
? lastStateRef.current
|
|
509
|
-
: router.getRehydratedState(lastStateRef.current, {
|
|
513
|
+
if (lastState && isStateValid(lastState)) {
|
|
514
|
+
const state: State = isStateInitialized(lastState)
|
|
515
|
+
? lastState
|
|
516
|
+
: router.getRehydratedState(lastState, {
|
|
510
517
|
routeNames,
|
|
511
518
|
routeParamList,
|
|
512
519
|
routeGetIdList,
|
|
@@ -607,7 +614,7 @@ export function useNavigationBuilder<
|
|
|
607
614
|
// that some changes to routeConfigs are explicitly ignored, such as changes
|
|
608
615
|
// to initialParams
|
|
609
616
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
610
|
-
}, [currentState, router, isStateValid]);
|
|
617
|
+
}, [currentState, lastState, router, isStateValid]);
|
|
611
618
|
|
|
612
619
|
const previousRouteKeyListRef = React.useRef(routeKeyList);
|
|
613
620
|
|
|
@@ -647,7 +654,7 @@ export function useNavigationBuilder<
|
|
|
647
654
|
// If the state isn't initialized, or stale, use the state we initialized instead
|
|
648
655
|
// The state won't update until there's a change needed in the state we have initialized locally
|
|
649
656
|
// So it'll be `undefined` or stale until the first navigation event happens
|
|
650
|
-
isStateInitialized(currentState)
|
|
657
|
+
lastState === undefined && isStateInitialized(currentState)
|
|
651
658
|
? (currentState as State)
|
|
652
659
|
: (initializedState as State);
|
|
653
660
|
|
|
@@ -772,7 +779,7 @@ export function useNavigationBuilder<
|
|
|
772
779
|
: nextState;
|
|
773
780
|
}
|
|
774
781
|
|
|
775
|
-
React.
|
|
782
|
+
React.useInsertionEffect(() => {
|
|
776
783
|
if (
|
|
777
784
|
consumedParams &&
|
|
778
785
|
didConsumeNestedParams &&
|
|
@@ -801,24 +808,29 @@ export function useNavigationBuilder<
|
|
|
801
808
|
// So we override the state object we return to use the latest state as soon as possible
|
|
802
809
|
state = nextState;
|
|
803
810
|
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
811
|
+
React.useInsertionEffect(() => {
|
|
812
|
+
savedStateRef.current = state;
|
|
813
|
+
previousStateRef.current = currentState;
|
|
807
814
|
});
|
|
808
815
|
|
|
809
|
-
|
|
816
|
+
React.useInsertionEffect(() => {
|
|
817
|
+
return () => {
|
|
818
|
+
// An initially hidden Activity doesn't run passive effects or their cleanup.
|
|
819
|
+
// We mark actual unmount here so queued updates can't write state into a replacement navigator.
|
|
820
|
+
mountStateRef.current = 'unmounted';
|
|
821
|
+
};
|
|
822
|
+
}, []);
|
|
810
823
|
|
|
811
824
|
React.useEffect(() => {
|
|
825
|
+
const savedState = savedStateRef.current ?? state;
|
|
826
|
+
|
|
812
827
|
// In strict mode, React will double-invoke effects.
|
|
813
|
-
// So we need to reset the
|
|
814
|
-
|
|
828
|
+
// So we need to reset the status if component was not unmounted
|
|
829
|
+
mountStateRef.current = 'mounted';
|
|
815
830
|
|
|
816
831
|
setKey(navigatorKey);
|
|
817
832
|
|
|
818
|
-
if (
|
|
819
|
-
(!getIsInitial() || getIsStateEmitted()) &&
|
|
820
|
-
lastNotifiedStateRef.current !== state
|
|
821
|
-
) {
|
|
833
|
+
if (!getIsInitial() || getIsStateEmitted()) {
|
|
822
834
|
// We need to notify the state update in these scenarios:
|
|
823
835
|
// 1. If it's not the initial render of the component containing the navigator
|
|
824
836
|
// 2. If the container has already emitted state before the navigator mounted
|
|
@@ -831,31 +843,25 @@ export function useNavigationBuilder<
|
|
|
831
843
|
// So only checking for the initial render is not enough.
|
|
832
844
|
// We also need to propagate the navigator's initial state when it mounts later,
|
|
833
845
|
// so the container is notified that the nested state is now available.
|
|
834
|
-
|
|
835
|
-
// We only notify if the state is different from what we already notified
|
|
836
|
-
// Otherwise, this goes into a loop when inside `<Activity mode="hidden">`
|
|
837
|
-
setState(state);
|
|
838
|
-
|
|
839
|
-
lastNotifiedStateRef.current = state;
|
|
846
|
+
setState(savedState);
|
|
840
847
|
}
|
|
841
848
|
|
|
842
849
|
return () => {
|
|
843
850
|
// We need to clean up state for this navigator on unmount
|
|
844
851
|
if (getCurrentState() !== undefined && getKey() === navigatorKey) {
|
|
845
852
|
setCurrentState(undefined);
|
|
846
|
-
stateCleanupRef.current = true;
|
|
847
853
|
}
|
|
848
854
|
|
|
849
|
-
|
|
850
|
-
// Without this, the guard above sees the same reference and skips setState,
|
|
851
|
-
// causing the initial state to be lost after cleanup wipes the container state.
|
|
852
|
-
lastNotifiedStateRef.current = null;
|
|
855
|
+
mountStateRef.current = 'unmounted';
|
|
853
856
|
};
|
|
854
857
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
855
858
|
}, []);
|
|
856
859
|
|
|
857
860
|
const getState = useLatestCallback((): State => {
|
|
858
|
-
const currentState =
|
|
861
|
+
const currentState =
|
|
862
|
+
mountStateRef.current === 'unmounted'
|
|
863
|
+
? savedStateRef.current
|
|
864
|
+
: getCurrentState();
|
|
859
865
|
|
|
860
866
|
return deepFreeze(
|
|
861
867
|
(isStateInitialized(currentState)
|
|
@@ -1028,8 +1034,8 @@ export function useNavigationBuilder<
|
|
|
1028
1034
|
ScreenOptions,
|
|
1029
1035
|
EventMap
|
|
1030
1036
|
>({
|
|
1031
|
-
routes: state.routes,
|
|
1032
1037
|
screens,
|
|
1038
|
+
state,
|
|
1033
1039
|
navigation,
|
|
1034
1040
|
screenOptions,
|
|
1035
1041
|
screenLayout,
|
|
@@ -1045,7 +1051,7 @@ export function useNavigationBuilder<
|
|
|
1045
1051
|
emitter,
|
|
1046
1052
|
});
|
|
1047
1053
|
|
|
1048
|
-
const
|
|
1054
|
+
const render = (children: React.ReactNode) => {
|
|
1049
1055
|
const focusedRoute = state.routes[state.index];
|
|
1050
1056
|
|
|
1051
1057
|
if (focusedRoute == null) {
|
|
@@ -1082,12 +1088,12 @@ export function useNavigationBuilder<
|
|
|
1082
1088
|
</NavigationHelpersContext.Provider>
|
|
1083
1089
|
</NavigationMetaContext.Provider>
|
|
1084
1090
|
);
|
|
1085
|
-
}
|
|
1091
|
+
};
|
|
1086
1092
|
|
|
1087
1093
|
return {
|
|
1088
1094
|
state,
|
|
1089
1095
|
navigation,
|
|
1090
1096
|
descriptors,
|
|
1091
|
-
|
|
1097
|
+
render,
|
|
1092
1098
|
};
|
|
1093
1099
|
}
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
} from '@react-navigation/routers';
|
|
8
8
|
import * as React from 'react';
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { useNavigationBuilderContext } from './NavigationBuilderContext';
|
|
11
11
|
import { NavigationContext } from './NavigationProvider';
|
|
12
12
|
import type { NavigationHelpers, NavigationProp } from './types';
|
|
13
13
|
import type { NavigationEventEmitter } from './useEventEmitter';
|
|
@@ -72,128 +72,150 @@ export function useNavigationCache<
|
|
|
72
72
|
router,
|
|
73
73
|
emitter,
|
|
74
74
|
}: Options<State, ScreenOptions, EventMap>) {
|
|
75
|
-
const
|
|
75
|
+
const { withStackTrace } = useNavigationBuilderContext();
|
|
76
76
|
|
|
77
|
-
const
|
|
77
|
+
const parentNavigation = React.use(NavigationContext);
|
|
78
78
|
|
|
79
79
|
// Cache object which holds navigation objects for each screen
|
|
80
80
|
// We use `React.useMemo` instead of `React.useRef` coz we want to invalidate it when deps change
|
|
81
81
|
// In reality, these deps will rarely change, if ever
|
|
82
|
-
const cache = React.useMemo
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
EventMap,
|
|
88
|
-
ActionHelpers
|
|
89
|
-
>,
|
|
90
|
-
}),
|
|
82
|
+
const cache = React.useMemo<{
|
|
83
|
+
current: NavigationCache<State, ScreenOptions, EventMap, ActionHelpers>;
|
|
84
|
+
routeNames: Record<string, string>;
|
|
85
|
+
}>(
|
|
86
|
+
() => ({ current: {}, routeNames: {} }),
|
|
91
87
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
92
|
-
[
|
|
88
|
+
[
|
|
89
|
+
getState,
|
|
90
|
+
parentNavigation,
|
|
91
|
+
navigation,
|
|
92
|
+
setOptions,
|
|
93
|
+
router,
|
|
94
|
+
emitter,
|
|
95
|
+
withStackTrace,
|
|
96
|
+
]
|
|
93
97
|
);
|
|
94
98
|
|
|
95
|
-
const next =
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
99
|
+
const next = React.useMemo(() => {
|
|
100
|
+
return routes.reduce<
|
|
101
|
+
NavigationCache<State, ScreenOptions, EventMap, ActionHelpers>
|
|
102
|
+
>((acc, route) => {
|
|
103
|
+
const previous =
|
|
104
|
+
cache.routeNames[route.key] === route.name
|
|
105
|
+
? cache.current[route.key]
|
|
106
|
+
: undefined;
|
|
107
|
+
|
|
108
|
+
if (previous) {
|
|
109
|
+
// If a cached navigation object already exists, reuse it
|
|
110
|
+
acc[route.key] = previous;
|
|
111
|
+
} else {
|
|
112
|
+
const dispatch = (
|
|
113
|
+
thunk:
|
|
114
|
+
| NavigationAction
|
|
115
|
+
| ((state: State) => NavigationAction | null | undefined)
|
|
116
|
+
) => {
|
|
117
|
+
withStackTrace(dispatch, () => {
|
|
118
|
+
React.startTransition(() => {
|
|
119
|
+
const action =
|
|
120
|
+
typeof thunk === 'function' ? thunk(getState()) : thunk;
|
|
121
|
+
|
|
122
|
+
if (action != null) {
|
|
123
|
+
navigation.dispatch({ source: route.key, ...action });
|
|
124
|
+
}
|
|
125
|
+
});
|
|
117
126
|
});
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
dispatch(actions[name](...args))
|
|
132
|
-
);
|
|
133
|
-
|
|
134
|
-
acc[name] = helper;
|
|
135
|
-
|
|
136
|
-
return acc;
|
|
137
|
-
},
|
|
138
|
-
{}
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
142
|
-
const { emit, ...rest } = navigation;
|
|
143
|
-
|
|
144
|
-
acc[route.key] = {
|
|
145
|
-
...rest,
|
|
146
|
-
...helpers,
|
|
147
|
-
// FIXME: too much work to fix the types for now
|
|
148
|
-
...(emitter.create(route.key) as any),
|
|
149
|
-
dispatch,
|
|
150
|
-
getParent: (routeName) => {
|
|
151
|
-
if (routeName === route.name) {
|
|
152
|
-
// If the passed route name is the same as the current route's name,
|
|
153
|
-
// we return the cached navigation object for the route
|
|
154
|
-
return acc[route.key];
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
if (routeName !== undefined) {
|
|
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?`
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const actions = {
|
|
130
|
+
...router.actionCreators,
|
|
131
|
+
...CommonActions,
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const helpers = Object.keys(actions).reduce<Record<string, () => void>>(
|
|
135
|
+
(acc, name) => {
|
|
136
|
+
const helper = (...args: any) =>
|
|
137
|
+
withStackTrace(helper, () =>
|
|
138
|
+
// @ts-expect-error: name is a valid key, but TypeScript is dumb
|
|
139
|
+
dispatch(actions[name](...args))
|
|
163
140
|
);
|
|
141
|
+
|
|
142
|
+
acc[name] = helper;
|
|
143
|
+
|
|
144
|
+
return acc;
|
|
145
|
+
},
|
|
146
|
+
{}
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
150
|
+
const { emit, ...rest } = navigation;
|
|
151
|
+
|
|
152
|
+
acc[route.key] = {
|
|
153
|
+
...rest,
|
|
154
|
+
...helpers,
|
|
155
|
+
// FIXME: too much work to fix the types for now
|
|
156
|
+
...(emitter.create(route.key) as any),
|
|
157
|
+
dispatch,
|
|
158
|
+
getParent: (routeName) => {
|
|
159
|
+
if (routeName === route.name) {
|
|
160
|
+
// If the passed route name is the same as the current route's name,
|
|
161
|
+
// we return the cached navigation object for the route
|
|
162
|
+
return acc[route.key];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (routeName !== undefined) {
|
|
166
|
+
const parent = parentNavigation?.getParent(routeName);
|
|
167
|
+
|
|
168
|
+
if (parent === undefined) {
|
|
169
|
+
throw new Error(
|
|
170
|
+
`Couldn't find a navigation object for '${routeName}' in current or any parent screens. Is your component inside the correct screen?`
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return parent;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return parentNavigation;
|
|
178
|
+
},
|
|
179
|
+
setOptions: (options: Partial<ScreenOptions>) => {
|
|
180
|
+
setOptions((o) => ({
|
|
181
|
+
...o,
|
|
182
|
+
[route.key]: { ...o[route.key], ...options },
|
|
183
|
+
}));
|
|
184
|
+
},
|
|
185
|
+
isFocused: () => {
|
|
186
|
+
const state = rest.getState();
|
|
187
|
+
|
|
188
|
+
if (state.routes[state.index]?.key !== route.key) {
|
|
189
|
+
return false;
|
|
164
190
|
}
|
|
165
191
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
return navigation ? navigation.isFocused() : true;
|
|
187
|
-
},
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
return acc;
|
|
192
|
-
}, {});
|
|
192
|
+
// If the current screen is focused, we also need to check if parent navigator is focused
|
|
193
|
+
// This makes sure that we return the focus state in the whole tree, not just this navigator
|
|
194
|
+
return navigation ? navigation.isFocused() : true;
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return acc;
|
|
200
|
+
}, {});
|
|
201
|
+
}, [
|
|
202
|
+
cache,
|
|
203
|
+
routes,
|
|
204
|
+
getState,
|
|
205
|
+
navigation,
|
|
206
|
+
router,
|
|
207
|
+
withStackTrace,
|
|
208
|
+
emitter,
|
|
209
|
+
parentNavigation,
|
|
210
|
+
setOptions,
|
|
211
|
+
]);
|
|
193
212
|
|
|
194
213
|
React.useInsertionEffect(() => {
|
|
195
214
|
cache.current = next;
|
|
196
|
-
|
|
215
|
+
cache.routeNames = Object.fromEntries(
|
|
216
|
+
routes.map((route) => [route.key, route.name])
|
|
217
|
+
);
|
|
218
|
+
}, [cache, next, routes]);
|
|
197
219
|
|
|
198
220
|
return next;
|
|
199
221
|
}
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
} from '@react-navigation/routers';
|
|
8
8
|
import * as React from 'react';
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { useNavigationBuilderContext } from './NavigationBuilderContext';
|
|
11
11
|
import { NavigationContext } from './NavigationProvider';
|
|
12
12
|
import { type NavigationHelpers, PrivateValueStore } from './types';
|
|
13
13
|
import type { NavigationEventEmitter } from './useEventEmitter';
|
|
@@ -41,7 +41,7 @@ export function useNavigationHelpers<
|
|
|
41
41
|
router,
|
|
42
42
|
}: Options<State, Action>) {
|
|
43
43
|
const parentNavigationHelpers = React.use(NavigationContext);
|
|
44
|
-
const { withStackTrace } =
|
|
44
|
+
const { withStackTrace } = useNavigationBuilderContext();
|
|
45
45
|
|
|
46
46
|
return React.useMemo(() => {
|
|
47
47
|
const dispatch = (op: Action | ((state: State) => Action)) => {
|