@react-navigation/core 8.0.0-alpha.26 → 8.0.0-alpha.28
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 +12 -6
- package/lib/module/BaseNavigationContainer.js.map +1 -1
- package/lib/module/ConsumedParamsContext.js.map +1 -1
- package/lib/module/DataLoading.js +29 -4
- package/lib/module/DataLoading.js.map +1 -1
- package/lib/module/NavigationIndependentTree.js +8 -4
- package/lib/module/NavigationIndependentTree.js.map +1 -1
- package/lib/module/SceneView.js +16 -39
- package/lib/module/SceneView.js.map +1 -1
- package/lib/module/StaticNavigation.js +4 -3
- package/lib/module/StaticNavigation.js.map +1 -1
- package/lib/module/findFocusedRoute.js +1 -1
- package/lib/module/findFocusedRoute.js.map +1 -1
- package/lib/module/getActionFromState.js +1 -2
- package/lib/module/getActionFromState.js.map +1 -1
- package/lib/module/getPathFromState.js +41 -26
- package/lib/module/getPathFromState.js.map +1 -1
- package/lib/module/getPatternParts.js +15 -2
- package/lib/module/getPatternParts.js.map +1 -1
- package/lib/module/getStateFromPath.js +45 -19
- 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/useFocusEvents.js +6 -2
- package/lib/module/useFocusEvents.js.map +1 -1
- package/lib/module/useLazyValue.js.map +1 -1
- package/lib/module/useNavigationBuilder.js +51 -27
- package/lib/module/useNavigationBuilder.js.map +1 -1
- package/lib/module/useOnAction.js +25 -17
- package/lib/module/useOnAction.js.map +1 -1
- package/lib/module/useOnPreventRemove.js +1 -3
- package/lib/module/useOnPreventRemove.js.map +1 -1
- package/lib/module/useRouteCache.js +1 -3
- package/lib/module/useRouteCache.js.map +1 -1
- package/lib/module/useSyncState.js +3 -2
- package/lib/module/useSyncState.js.map +1 -1
- package/lib/typescript/src/BaseNavigationContainer.d.ts.map +1 -1
- package/lib/typescript/src/ConsumedParamsContext.d.ts +1 -6
- package/lib/typescript/src/ConsumedParamsContext.d.ts.map +1 -1
- package/lib/typescript/src/DataLoading.d.ts +1 -0
- package/lib/typescript/src/DataLoading.d.ts.map +1 -1
- package/lib/typescript/src/NavigationIndependentTree.d.ts.map +1 -1
- package/lib/typescript/src/SceneView.d.ts.map +1 -1
- package/lib/typescript/src/StaticNavigation.d.ts +39 -30
- package/lib/typescript/src/StaticNavigation.d.ts.map +1 -1
- package/lib/typescript/src/getPathFromState.d.ts.map +1 -1
- 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 +2 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +2 -2
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/useFocusEvents.d.ts.map +1 -1
- package/lib/typescript/src/useLazyValue.d.ts.map +1 -1
- package/lib/typescript/src/useNavigationBuilder.d.ts.map +1 -1
- package/lib/typescript/src/useOnAction.d.ts.map +1 -1
- package/lib/typescript/src/useOnPreventRemove.d.ts.map +1 -1
- package/lib/typescript/src/useRouteCache.d.ts.map +1 -1
- package/lib/typescript/src/useSyncState.d.ts.map +1 -1
- package/lib/typescript/src/utilities.d.ts +10 -0
- package/lib/typescript/src/utilities.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/BaseNavigationContainer.tsx +13 -7
- package/src/ConsumedParamsContext.tsx +1 -6
- package/src/DataLoading.tsx +68 -4
- package/src/NavigationIndependentTree.tsx +8 -5
- package/src/SceneView.tsx +18 -52
- package/src/StaticNavigation.tsx +88 -64
- package/src/findFocusedRoute.tsx +1 -1
- package/src/getActionFromState.tsx +7 -3
- package/src/getPathFromState.tsx +59 -32
- package/src/getPatternParts.tsx +15 -2
- package/src/getStateFromPath.tsx +77 -17
- package/src/index.tsx +5 -0
- package/src/types.tsx +2 -2
- package/src/useDescriptors.tsx +1 -1
- package/src/useFocusEvents.tsx +7 -5
- package/src/useLazyValue.tsx +2 -2
- package/src/useNavigationBuilder.tsx +82 -37
- package/src/useOnAction.tsx +36 -40
- package/src/useOnPreventRemove.tsx +4 -3
- package/src/useRouteCache.tsx +1 -4
- package/src/useSyncState.tsx +3 -2
- package/src/utilities.tsx +19 -0
|
@@ -19,7 +19,6 @@ import { ConsumedParamsContext } from './ConsumedParamsContext';
|
|
|
19
19
|
import { deepFreeze } from './deepFreeze';
|
|
20
20
|
import { Group } from './Group';
|
|
21
21
|
import { isArrayEqual } from './isArrayEqual';
|
|
22
|
-
import { isRecordEqual } from './isRecordEqual';
|
|
23
22
|
import { NavigationBuilderContext } from './NavigationBuilderContext';
|
|
24
23
|
import { NavigationHelpersContext } from './NavigationHelpersContext';
|
|
25
24
|
import { NavigationMetaContext } from './NavigationMetaContext';
|
|
@@ -104,7 +103,7 @@ const getRouteConfigsFromChildren = <
|
|
|
104
103
|
EventMap extends EventMapBase,
|
|
105
104
|
>(
|
|
106
105
|
children: React.ReactNode,
|
|
107
|
-
|
|
106
|
+
groupKeys: string[] = [],
|
|
108
107
|
groupOptions?: ScreenConfigWithParent<
|
|
109
108
|
State,
|
|
110
109
|
ScreenOptions,
|
|
@@ -147,7 +146,7 @@ const getRouteConfigsFromChildren = <
|
|
|
147
146
|
}
|
|
148
147
|
|
|
149
148
|
acc.push({
|
|
150
|
-
keys: [
|
|
149
|
+
keys: [...groupKeys, child.props.navigationKey],
|
|
151
150
|
options: groupOptions,
|
|
152
151
|
layout: groupLayout,
|
|
153
152
|
props: child.props as RouteConfig<
|
|
@@ -177,7 +176,9 @@ const getRouteConfigsFromChildren = <
|
|
|
177
176
|
acc.push(
|
|
178
177
|
...getRouteConfigsFromChildren<State, ScreenOptions, EventMap>(
|
|
179
178
|
child.props.children as React.ReactNode,
|
|
180
|
-
child.props.navigationKey
|
|
179
|
+
child.props.navigationKey != null
|
|
180
|
+
? [...groupKeys, child.props.navigationKey]
|
|
181
|
+
: groupKeys,
|
|
181
182
|
// FIXME
|
|
182
183
|
// @ts-expect-error: add validation
|
|
183
184
|
child.type !== Group
|
|
@@ -285,10 +286,23 @@ const getRouteConfigsFromChildren = <
|
|
|
285
286
|
return configs;
|
|
286
287
|
};
|
|
287
288
|
|
|
288
|
-
const getStateFromParams = (
|
|
289
|
+
const getStateFromParams = (
|
|
290
|
+
params: NavigatorRoute['params'],
|
|
291
|
+
type: string
|
|
292
|
+
): NavigationState | PartialState<NavigationState> | undefined => {
|
|
289
293
|
const state = params?.state;
|
|
290
294
|
|
|
291
295
|
if (isNavigationState(state)) {
|
|
296
|
+
if (state.type != null && state.type !== type) {
|
|
297
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
298
|
+
console.error(
|
|
299
|
+
`The state passed in the params is for a navigator of type '${state.type}', but it was passed to a navigator of type '${type}'. This can happen if the state was meant for a different navigator. The state will be discarded.`
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return undefined;
|
|
304
|
+
}
|
|
305
|
+
|
|
292
306
|
return state;
|
|
293
307
|
} else if (typeof params?.screen === 'string' && params?.initial !== false) {
|
|
294
308
|
return {
|
|
@@ -337,7 +351,7 @@ export function useNavigationBuilder<
|
|
|
337
351
|
|
|
338
352
|
const isNestedParamsConsumed =
|
|
339
353
|
typeof route?.params === 'object' && route.params != null
|
|
340
|
-
? consumedParams?.
|
|
354
|
+
? consumedParams?.has(route.params)
|
|
341
355
|
: false;
|
|
342
356
|
|
|
343
357
|
const {
|
|
@@ -395,7 +409,7 @@ export function useNavigationBuilder<
|
|
|
395
409
|
ScreenConfigWithParent<State, ScreenOptions, EventMap>
|
|
396
410
|
> = {};
|
|
397
411
|
|
|
398
|
-
const routeKeyList: Record<string,
|
|
412
|
+
const routeKeyList: Record<string, (string | undefined)[]> = {};
|
|
399
413
|
const routeParamList: Record<string, object | undefined> = {};
|
|
400
414
|
const routeGetIdList: RouterConfigOptions['routeGetIdList'] = {};
|
|
401
415
|
|
|
@@ -409,7 +423,7 @@ export function useNavigationBuilder<
|
|
|
409
423
|
}
|
|
410
424
|
|
|
411
425
|
screens[name] = config;
|
|
412
|
-
routeKeyList[name] = config.keys
|
|
426
|
+
routeKeyList[name] = config.keys;
|
|
413
427
|
routeParamList[name] = config.props.initialParams;
|
|
414
428
|
|
|
415
429
|
Object.assign(routeGetIdList, { [name]: config.props.getId });
|
|
@@ -543,7 +557,7 @@ export function useNavigationBuilder<
|
|
|
543
557
|
} else {
|
|
544
558
|
const paramsForState = isNestedParamsConsumed ? undefined : route?.params;
|
|
545
559
|
const stateFromParams = paramsForState
|
|
546
|
-
? getStateFromParams(paramsForState)
|
|
560
|
+
? getStateFromParams(paramsForState, router.type)
|
|
547
561
|
: undefined;
|
|
548
562
|
|
|
549
563
|
const stateBeforeInitialization = (stateFromParams ?? currentState) as
|
|
@@ -565,13 +579,21 @@ export function useNavigationBuilder<
|
|
|
565
579
|
|
|
566
580
|
if (
|
|
567
581
|
stateBeforeInitialization != null &&
|
|
582
|
+
isStateValid(stateBeforeInitialization) &&
|
|
568
583
|
options.routeNamesChangeBehavior === 'lastUnhandled' &&
|
|
569
584
|
doesStateHaveOnlyInvalidRoutes(stateBeforeInitialization)
|
|
570
585
|
) {
|
|
571
586
|
return [stateBeforeInitialization, hydratedState, true, paramsForState];
|
|
572
587
|
}
|
|
573
588
|
|
|
574
|
-
return [
|
|
589
|
+
return [
|
|
590
|
+
undefined,
|
|
591
|
+
hydratedState,
|
|
592
|
+
false,
|
|
593
|
+
// Current state is already initialized
|
|
594
|
+
// So params weren't used, and need to be handled later
|
|
595
|
+
isStateInitialized(currentState) ? undefined : paramsForState,
|
|
596
|
+
];
|
|
575
597
|
}
|
|
576
598
|
// We explicitly don't include routeNames, route.params etc. in the dep list
|
|
577
599
|
// below. We want to avoid forcing a new state to be calculated in those cases
|
|
@@ -589,6 +611,16 @@ export function useNavigationBuilder<
|
|
|
589
611
|
|
|
590
612
|
const previousRouteKeyList = previousRouteKeyListRef.current;
|
|
591
613
|
|
|
614
|
+
// Names of the screens whose navigation key changed since the last render
|
|
615
|
+
const routeKeyChanges = Object.keys(routeKeyList).filter((name) => {
|
|
616
|
+
const current = routeKeyList[name];
|
|
617
|
+
const previous = previousRouteKeyList[name];
|
|
618
|
+
|
|
619
|
+
return (
|
|
620
|
+
current != null && previous != null && !isArrayEqual(current, previous)
|
|
621
|
+
);
|
|
622
|
+
});
|
|
623
|
+
|
|
592
624
|
const [unhandledState, setUnhandledState] = React.useState<
|
|
593
625
|
NavigationState | PartialState<NavigationState> | undefined
|
|
594
626
|
>(stateBeforeInitialization);
|
|
@@ -616,10 +648,14 @@ export function useNavigationBuilder<
|
|
|
616
648
|
let nextState: State = state;
|
|
617
649
|
let shouldClearUnhandledState = false;
|
|
618
650
|
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
651
|
+
if (unhandledState != null && !isStateValid(unhandledState)) {
|
|
652
|
+
// The stored unhandled state is for a different type of navigator
|
|
653
|
+
// It can't be used with this navigator, so we discard it
|
|
654
|
+
setUnhandledState(undefined);
|
|
655
|
+
} else if (
|
|
656
|
+
// Previously unhandled state is now valid again
|
|
657
|
+
// And current state no longer has any valid routes
|
|
658
|
+
// We should reuse the unhandled state instead of re-calculating the state
|
|
623
659
|
unhandledState?.routes.every((r) => routeNames.includes(r.name)) &&
|
|
624
660
|
state?.routes.every((r) => !routeNames.includes(r.name))
|
|
625
661
|
) {
|
|
@@ -634,18 +670,14 @@ export function useNavigationBuilder<
|
|
|
634
670
|
);
|
|
635
671
|
} else if (
|
|
636
672
|
!isArrayEqual(state.routeNames, routeNames) ||
|
|
637
|
-
|
|
673
|
+
routeKeyChanges.length !== 0
|
|
638
674
|
) {
|
|
639
675
|
// When the list of route names change, the router should handle it to remove invalid routes
|
|
640
676
|
nextState = router.getStateForRouteNamesChange(state, {
|
|
641
677
|
routeNames,
|
|
642
678
|
routeParamList,
|
|
643
679
|
routeGetIdList,
|
|
644
|
-
routeKeyChanges
|
|
645
|
-
(name) =>
|
|
646
|
-
name in previousRouteKeyList &&
|
|
647
|
-
routeKeyList[name] !== previousRouteKeyList[name]
|
|
648
|
-
),
|
|
680
|
+
routeKeyChanges,
|
|
649
681
|
});
|
|
650
682
|
}
|
|
651
683
|
|
|
@@ -653,21 +685,24 @@ export function useNavigationBuilder<
|
|
|
653
685
|
|
|
654
686
|
if (route?.params && !didConsumeNestedParams) {
|
|
655
687
|
let action: CommonActions.Action | undefined;
|
|
656
|
-
const stateFromParams = route.params.state;
|
|
657
688
|
|
|
658
|
-
if (isNavigationState(
|
|
689
|
+
if (isNavigationState(route.params.state) && !isNestedParamsConsumed) {
|
|
659
690
|
didConsumeNestedParams = true;
|
|
660
691
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
692
|
+
const stateFromParams = getStateFromParams(route.params, router.type);
|
|
693
|
+
|
|
694
|
+
if (stateFromParams != null) {
|
|
695
|
+
if (
|
|
696
|
+
options.routeNamesChangeBehavior === 'lastUnhandled' &&
|
|
697
|
+
doesStateHaveOnlyInvalidRoutes(stateFromParams)
|
|
698
|
+
) {
|
|
699
|
+
if (stateFromParams !== unhandledState) {
|
|
700
|
+
setUnhandledState(stateFromParams);
|
|
701
|
+
}
|
|
702
|
+
} else {
|
|
703
|
+
// If the route was updated with new state, we should reset to it
|
|
704
|
+
action = CommonActions.reset(stateFromParams);
|
|
667
705
|
}
|
|
668
|
-
} else {
|
|
669
|
-
// If the route was updated with new state, we should reset to it
|
|
670
|
-
action = CommonActions.reset(stateFromParams);
|
|
671
706
|
}
|
|
672
707
|
} else if (
|
|
673
708
|
typeof route.params.screen === 'string' &&
|
|
@@ -680,7 +715,7 @@ export function useNavigationBuilder<
|
|
|
680
715
|
options.routeNamesChangeBehavior === 'lastUnhandled' &&
|
|
681
716
|
!routeNames.includes(route.params.screen)
|
|
682
717
|
) {
|
|
683
|
-
const state = getStateFromParams(route.params);
|
|
718
|
+
const state = getStateFromParams(route.params, router.type);
|
|
684
719
|
|
|
685
720
|
if (state != null && !deepEqual(state, unhandledState)) {
|
|
686
721
|
setUnhandledState(state);
|
|
@@ -709,6 +744,18 @@ export function useNavigationBuilder<
|
|
|
709
744
|
})
|
|
710
745
|
: null;
|
|
711
746
|
|
|
747
|
+
if (
|
|
748
|
+
process.env.NODE_ENV !== 'production' &&
|
|
749
|
+
action != null &&
|
|
750
|
+
updatedState === null
|
|
751
|
+
) {
|
|
752
|
+
const names = routeNames.map((name) => `'${name}'`).join(', ');
|
|
753
|
+
|
|
754
|
+
console.error(
|
|
755
|
+
`The 'state' or 'screen' passed in the params couldn't be applied to the navigator with the screens: ${names}. This can happen if the params contain names of screens that don't exist in the navigator. The navigation specified in the params will be ignored.`
|
|
756
|
+
);
|
|
757
|
+
}
|
|
758
|
+
|
|
712
759
|
nextState =
|
|
713
760
|
updatedState !== null
|
|
714
761
|
? router.getRehydratedState(updatedState, {
|
|
@@ -719,18 +766,16 @@ export function useNavigationBuilder<
|
|
|
719
766
|
: nextState;
|
|
720
767
|
}
|
|
721
768
|
|
|
722
|
-
const setConsumedParams = consumedParams?.setConsumed;
|
|
723
|
-
|
|
724
769
|
React.useEffect(() => {
|
|
725
770
|
if (
|
|
726
|
-
|
|
771
|
+
consumedParams &&
|
|
727
772
|
didConsumeNestedParams &&
|
|
728
773
|
typeof route?.params === 'object' &&
|
|
729
774
|
route.params != null
|
|
730
775
|
) {
|
|
731
|
-
|
|
776
|
+
consumedParams.set(route.params, true);
|
|
732
777
|
}
|
|
733
|
-
}, [didConsumeNestedParams, route?.params
|
|
778
|
+
}, [consumedParams, didConsumeNestedParams, route?.params]);
|
|
734
779
|
|
|
735
780
|
const shouldUpdate = state !== nextState;
|
|
736
781
|
|
package/src/useOnAction.tsx
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
NavigationAction,
|
|
3
|
-
NavigationRoute,
|
|
4
3
|
NavigationState,
|
|
5
|
-
ParamListBase,
|
|
6
|
-
PartialRoute,
|
|
7
4
|
PartialState,
|
|
8
5
|
Router,
|
|
9
6
|
RouterConfigOptions,
|
|
10
7
|
} from '@react-navigation/routers';
|
|
11
8
|
import * as React from 'react';
|
|
12
9
|
|
|
13
|
-
import {
|
|
10
|
+
import { ConsumedParamsContext } from './ConsumedParamsContext';
|
|
11
|
+
import { getLoaderForStateChange } from './DataLoading';
|
|
14
12
|
import {
|
|
15
13
|
type ChildActionListener,
|
|
16
14
|
type ChildBeforeRemoveListener,
|
|
@@ -32,40 +30,6 @@ type Options<State extends NavigationState> = {
|
|
|
32
30
|
emitter: NavigationEventEmitter<EventMapCore<any>>;
|
|
33
31
|
};
|
|
34
32
|
|
|
35
|
-
type MaybePartialRoute =
|
|
36
|
-
| NavigationRoute<ParamListBase, string>
|
|
37
|
-
| PartialRoute<NavigationRoute<ParamListBase, string>>;
|
|
38
|
-
|
|
39
|
-
const didFocusedRouteChange = (
|
|
40
|
-
currentState: NavigationState | PartialState<NavigationState>,
|
|
41
|
-
nextState: NavigationState | PartialState<NavigationState>
|
|
42
|
-
) => {
|
|
43
|
-
let currentFocusedRoute: MaybePartialRoute | undefined =
|
|
44
|
-
currentState.routes[currentState.index ?? 0];
|
|
45
|
-
|
|
46
|
-
let nextFocusedRoute: MaybePartialRoute | undefined =
|
|
47
|
-
nextState.routes[nextState.index ?? 0];
|
|
48
|
-
|
|
49
|
-
let changed = currentFocusedRoute?.key !== nextFocusedRoute?.key;
|
|
50
|
-
|
|
51
|
-
while (
|
|
52
|
-
!changed &&
|
|
53
|
-
currentFocusedRoute?.state != null &&
|
|
54
|
-
nextFocusedRoute?.state != null &&
|
|
55
|
-
currentFocusedRoute.state !== nextFocusedRoute.state
|
|
56
|
-
) {
|
|
57
|
-
currentFocusedRoute =
|
|
58
|
-
currentFocusedRoute.state.routes[currentFocusedRoute.state.index ?? 0];
|
|
59
|
-
|
|
60
|
-
nextFocusedRoute =
|
|
61
|
-
nextFocusedRoute.state.routes[nextFocusedRoute.state.index ?? 0];
|
|
62
|
-
|
|
63
|
-
changed = currentFocusedRoute?.key !== nextFocusedRoute?.key;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return changed;
|
|
67
|
-
};
|
|
68
|
-
|
|
69
33
|
/**
|
|
70
34
|
* Hook to handle actions for a navigator, including state updates and bubbling.
|
|
71
35
|
*
|
|
@@ -90,9 +54,11 @@ export function useOnAction<State extends NavigationState>({
|
|
|
90
54
|
onRouteFocus: onRouteFocusParent,
|
|
91
55
|
addListener: addListenerParent,
|
|
92
56
|
onDispatchAction,
|
|
57
|
+
flushUpdates,
|
|
93
58
|
} = React.use(NavigationBuilderContext);
|
|
94
59
|
|
|
95
60
|
const tree = React.use(StaticTreeContext);
|
|
61
|
+
const consumedParams = React.use(ConsumedParamsContext);
|
|
96
62
|
|
|
97
63
|
const routerConfigOptionsRef =
|
|
98
64
|
React.useRef<RouterConfigOptions>(routerConfigOptions);
|
|
@@ -106,6 +72,11 @@ export function useOnAction<State extends NavigationState>({
|
|
|
106
72
|
action: NavigationAction,
|
|
107
73
|
visitedNavigators: Set<string> = new Set<string>()
|
|
108
74
|
) => {
|
|
75
|
+
// Apply any pending state updates scheduled during render before handling the action
|
|
76
|
+
// Otherwise the action will be handled with an outdated state,
|
|
77
|
+
// and the pending updates will overwrite the changes from the action
|
|
78
|
+
flushUpdates();
|
|
79
|
+
|
|
109
80
|
const state = getState();
|
|
110
81
|
|
|
111
82
|
// Since actions can bubble both up and down, they could come to the same navigator again
|
|
@@ -128,6 +99,17 @@ export function useOnAction<State extends NavigationState>({
|
|
|
128
99
|
result =
|
|
129
100
|
result === null && action.target === state.key ? state : result;
|
|
130
101
|
|
|
102
|
+
if (result !== null && result.stale !== false) {
|
|
103
|
+
// Some actions (e.g. `RESET`) may return a stale state from the router
|
|
104
|
+
// We rehydrate it before storing so the stored state is always usable
|
|
105
|
+
// Otherwise anything reading the state before the next render
|
|
106
|
+
// (e.g. another dispatch immediately after) would get an outdated state
|
|
107
|
+
result = router.getRehydratedState(
|
|
108
|
+
result,
|
|
109
|
+
routerConfigOptionsRef.current
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
131
113
|
if (result !== null) {
|
|
132
114
|
if (state !== result) {
|
|
133
115
|
const isPrevented = shouldPreventRemove(
|
|
@@ -144,10 +126,22 @@ export function useOnAction<State extends NavigationState>({
|
|
|
144
126
|
return true;
|
|
145
127
|
}
|
|
146
128
|
|
|
129
|
+
if (getState() !== state) {
|
|
130
|
+
// Listeners for 'beforeRemove' may dispatch actions that change the state
|
|
131
|
+
// Then the result we have is outdated and would overwrite those changes
|
|
132
|
+
// So we handle the action again to calculate the result with the latest state
|
|
133
|
+
return onAction(action, new Set<string>());
|
|
134
|
+
}
|
|
135
|
+
|
|
147
136
|
onDispatchAction(action, false);
|
|
148
137
|
|
|
149
|
-
if (tree
|
|
150
|
-
const loader =
|
|
138
|
+
if (tree) {
|
|
139
|
+
const loader = getLoaderForStateChange(
|
|
140
|
+
tree,
|
|
141
|
+
result,
|
|
142
|
+
state,
|
|
143
|
+
consumedParams
|
|
144
|
+
);
|
|
151
145
|
|
|
152
146
|
loader?.();
|
|
153
147
|
}
|
|
@@ -194,7 +188,9 @@ export function useOnAction<State extends NavigationState>({
|
|
|
194
188
|
[
|
|
195
189
|
actionListeners,
|
|
196
190
|
beforeRemoveListeners,
|
|
191
|
+
consumedParams,
|
|
197
192
|
emitter,
|
|
193
|
+
flushUpdates,
|
|
198
194
|
getState,
|
|
199
195
|
key,
|
|
200
196
|
onActionParent,
|
|
@@ -39,9 +39,10 @@ export const shouldPreventRemove = (
|
|
|
39
39
|
.filter(([, route]) => route.key != null)
|
|
40
40
|
);
|
|
41
41
|
|
|
42
|
-
const visitedRouteKeys
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
const visitedRouteKeys =
|
|
43
|
+
VISITED_ROUTE_KEYS in action && action[VISITED_ROUTE_KEYS] instanceof Set
|
|
44
|
+
? action[VISITED_ROUTE_KEYS]
|
|
45
|
+
: new Set<string>();
|
|
45
46
|
|
|
46
47
|
const beforeRemoveAction = {
|
|
47
48
|
...action,
|
package/src/useRouteCache.tsx
CHANGED
|
@@ -41,10 +41,7 @@ export function useRouteCache<State extends NavigationState>(
|
|
|
41
41
|
// FIXME: since the state is updated with mutation, the route object cannot be frozen
|
|
42
42
|
// As a workaround, loop through the object and make the properties readonly
|
|
43
43
|
// Only needed once per proxy - skip if we're reusing a previously-frozen one
|
|
44
|
-
for (const key
|
|
45
|
-
// @ts-expect-error: this is fine since we are looping through the object
|
|
46
|
-
const value = proxy[key];
|
|
47
|
-
|
|
44
|
+
for (const [key, value] of Object.entries(proxy)) {
|
|
48
45
|
Object.defineProperty(proxy, key, {
|
|
49
46
|
enumerable: true,
|
|
50
47
|
configurable: true,
|
package/src/useSyncState.tsx
CHANGED
|
@@ -26,9 +26,10 @@ const createStore = <T,>(getInitialState: () => T) => {
|
|
|
26
26
|
|
|
27
27
|
const setState = (newState: T) => {
|
|
28
28
|
state = deepFreeze(newState);
|
|
29
|
-
didUpdate = true;
|
|
30
29
|
|
|
31
|
-
if (
|
|
30
|
+
if (isBatching) {
|
|
31
|
+
didUpdate = true;
|
|
32
|
+
} else {
|
|
32
33
|
listeners.forEach((listener) => listener());
|
|
33
34
|
}
|
|
34
35
|
};
|
package/src/utilities.tsx
CHANGED
|
@@ -26,6 +26,25 @@ export type NoExcessObject<Provided, Allowed> = Record<
|
|
|
26
26
|
never
|
|
27
27
|
>;
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Check an object captured as a generic type against an allowed type,
|
|
31
|
+
* e.g. an object returned from a callback isn't checked for excess properties.
|
|
32
|
+
*/
|
|
33
|
+
export type NoExcessCallbackReturn<
|
|
34
|
+
Provided extends {},
|
|
35
|
+
Allowed extends {},
|
|
36
|
+
> = Allowed & Provided & NoExcessObject<Provided, Allowed>;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Apply excess property checking to a value,
|
|
40
|
+
* or to the return value if the value is a function.
|
|
41
|
+
*/
|
|
42
|
+
export type NoExcessValue<Provided, Allowed extends {}> = Provided extends (
|
|
43
|
+
...args: never[]
|
|
44
|
+
) => infer Return
|
|
45
|
+
? (...args: never[]) => Allowed & NoExcessObject<Return, Allowed>
|
|
46
|
+
: NoExcessObject<Provided, Allowed>;
|
|
47
|
+
|
|
29
48
|
/**
|
|
30
49
|
* We get a union type when using keyof, but we want an intersection instead.
|
|
31
50
|
* https://stackoverflow.com/a/50375286/1665026
|