@react-navigation/core 8.0.0-alpha.34 → 8.0.0-alpha.36
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/NavigationIndependentTree.js +9 -6
- 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 +11 -7
- package/lib/module/getPathFromState.js.map +1 -1
- package/lib/module/getStateFromPath.js +1 -1
- package/lib/module/getStateFromPath.js.map +1 -1
- package/lib/module/queryString.js +71 -0
- package/lib/module/queryString.js.map +1 -0
- package/lib/module/useDescriptors.js +7 -4
- 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/useIsFocused.js +1 -0
- package/lib/module/useIsFocused.js.map +1 -1
- package/lib/module/useNavigationBuilder.js +40 -37
- package/lib/module/useNavigationBuilder.js.map +1 -1
- package/lib/module/useNavigationCache.js +82 -78
- package/lib/module/useNavigationCache.js.map +1 -1
- package/lib/module/useNavigationState.js +48 -23
- package/lib/module/useNavigationState.js.map +1 -1
- package/lib/module/useOptionsGetters.js +18 -17
- 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 +3 -1
- 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 +1 -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.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/queryString.d.ts +5 -0
- package/lib/typescript/src/queryString.d.ts.map +1 -0
- 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/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/package.json +2 -3
- package/src/BaseNavigationContainer.tsx +19 -2
- package/src/NavigationBuilderContext.tsx +1 -1
- package/src/NavigationIndependentTree.tsx +16 -8
- package/src/NavigationProvider.tsx +13 -4
- package/src/PreventRemoveProvider.tsx +2 -8
- package/src/SceneView.tsx +3 -1
- package/src/getPathFromState.tsx +7 -5
- package/src/getStateFromPath.tsx +1 -1
- package/src/queryString.ts +109 -0
- package/src/useDescriptors.tsx +18 -5
- package/src/useFocusEffect.tsx +5 -2
- package/src/useFocusEvents.tsx +5 -2
- package/src/useIsFocused.tsx +4 -0
- package/src/useNavigationBuilder.tsx +53 -45
- package/src/useNavigationCache.tsx +127 -105
- package/src/useNavigationState.tsx +60 -29
- package/src/useOptionsGetters.tsx +24 -24
- package/src/usePreventRemoveContext.tsx +1 -1
- package/src/useRouteCache.tsx +49 -33
- package/src/useScheduleUpdate.tsx +4 -1
- package/src/useSyncState.tsx +9 -11
- 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
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import type { ParamListBase, Route } from '@react-navigation/routers';
|
|
2
2
|
import * as React from 'react';
|
|
3
|
+
import useLatestCallback from 'use-latest-callback';
|
|
3
4
|
|
|
4
5
|
import type { NavigationProp } from './types';
|
|
5
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
FocusedRouteKeyContext,
|
|
8
|
+
IsFocusedContext,
|
|
9
|
+
IsFocusedGetterContext,
|
|
10
|
+
} from './useIsFocused';
|
|
6
11
|
import { useLazyValue } from './useLazyValue';
|
|
7
12
|
import { NamedNavigationStateListenerProvider } from './useNavigationState';
|
|
8
13
|
|
|
@@ -62,6 +67,8 @@ export function NavigationProvider({ route, navigation, children }: Props) {
|
|
|
62
67
|
? focusedRouteKey === route.key
|
|
63
68
|
: false;
|
|
64
69
|
|
|
70
|
+
const getIsFocused = useLatestCallback(() => isFocused);
|
|
71
|
+
|
|
65
72
|
return (
|
|
66
73
|
<NamedNavigationStateListenerProvider name={route.name}>
|
|
67
74
|
<NamedRouteContextListContext.Provider value={NamedRouteContextList}>
|
|
@@ -69,9 +76,11 @@ export function NavigationProvider({ route, navigation, children }: Props) {
|
|
|
69
76
|
<NavigationRouteContext.Provider value={route}>
|
|
70
77
|
<NavigationContext.Provider value={navigation}>
|
|
71
78
|
<IsFocusedContext.Provider value={isFocused}>
|
|
72
|
-
<
|
|
73
|
-
{
|
|
74
|
-
|
|
79
|
+
<IsFocusedGetterContext.Provider value={getIsFocused}>
|
|
80
|
+
<IsScreenContext.Provider value={true}>
|
|
81
|
+
{children}
|
|
82
|
+
</IsScreenContext.Provider>
|
|
83
|
+
</IsFocusedGetterContext.Provider>
|
|
75
84
|
</IsFocusedContext.Provider>
|
|
76
85
|
</NavigationContext.Provider>
|
|
77
86
|
</NavigationRouteContext.Provider>
|
|
@@ -50,15 +50,9 @@ export function PreventRemoveProvider({ children }: Props) {
|
|
|
50
50
|
|
|
51
51
|
const setPreventRemove = useLatestCallback(
|
|
52
52
|
(id: string, routeKey: string, preventRemove: boolean): void => {
|
|
53
|
-
if (
|
|
54
|
-
preventRemove &&
|
|
55
|
-
(navigation == null ||
|
|
56
|
-
navigation
|
|
57
|
-
?.getState()
|
|
58
|
-
.routes.every((route) => route.key !== routeKey))
|
|
59
|
-
) {
|
|
53
|
+
if (preventRemove && navigation == null) {
|
|
60
54
|
throw new Error(
|
|
61
|
-
|
|
55
|
+
"Couldn't find a navigation object. This is likely a bug in the navigator.\n\nIf you're using a custom navigator, make sure that the navigator content is wrapped by the 'render' function returned by 'useNavigationBuilder'."
|
|
62
56
|
);
|
|
63
57
|
}
|
|
64
58
|
|
package/src/SceneView.tsx
CHANGED
|
@@ -22,6 +22,7 @@ type Props<State extends NavigationState, ScreenOptions extends {}> = {
|
|
|
22
22
|
navigation: NavigationProp<ParamListBase, string, State, ScreenOptions>;
|
|
23
23
|
route: Route<string>;
|
|
24
24
|
routeState: NavigationState | PartialState<NavigationState> | undefined;
|
|
25
|
+
getFocusedRouteKey: () => string | undefined;
|
|
25
26
|
getState: () => State;
|
|
26
27
|
setState: (state: State) => void;
|
|
27
28
|
subscribe: (callback: () => void) => () => void;
|
|
@@ -41,6 +42,7 @@ export function SceneView<
|
|
|
41
42
|
route,
|
|
42
43
|
navigation,
|
|
43
44
|
routeState,
|
|
45
|
+
getFocusedRouteKey,
|
|
44
46
|
getState,
|
|
45
47
|
setState,
|
|
46
48
|
subscribe,
|
|
@@ -53,7 +55,7 @@ export function SceneView<
|
|
|
53
55
|
const { addOptionsGetter } = useOptionsGetters({
|
|
54
56
|
key: route.key,
|
|
55
57
|
options,
|
|
56
|
-
|
|
58
|
+
getFocusedRouteKey,
|
|
57
59
|
});
|
|
58
60
|
|
|
59
61
|
const setKey = React.useCallback((key: string) => {
|
package/src/getPathFromState.tsx
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { NavigationState, PartialState } from '@react-navigation/routers';
|
|
2
|
-
import queryString from 'query-string';
|
|
3
2
|
|
|
4
3
|
import {
|
|
5
4
|
combinePatternParts,
|
|
@@ -7,6 +6,7 @@ import {
|
|
|
7
6
|
type PatternPart,
|
|
8
7
|
} from './getPatternParts';
|
|
9
8
|
import { getStateFromRouteParams } from './getStateFromRouteParams';
|
|
9
|
+
import * as queryString from './queryString';
|
|
10
10
|
import type { PathConfig, PathConfigMap } from './types';
|
|
11
11
|
import { validatePathConfig } from './validatePathConfig';
|
|
12
12
|
|
|
@@ -226,7 +226,9 @@ export function getPathFromState<ParamList extends {}>(
|
|
|
226
226
|
if (route.params) {
|
|
227
227
|
const options = config;
|
|
228
228
|
const params = route.params as Record<string, unknown>;
|
|
229
|
-
const currentParams: Record<string, SerializedParamValue> = {
|
|
229
|
+
const currentParams: Record<string, SerializedParamValue> = {
|
|
230
|
+
__proto__: null,
|
|
231
|
+
};
|
|
230
232
|
|
|
231
233
|
for (const key in params) {
|
|
232
234
|
const value = params[key];
|
|
@@ -270,7 +272,7 @@ export function getPathFromState<ParamList extends {}>(
|
|
|
270
272
|
// If this is the focused route, keep the params for later use
|
|
271
273
|
// We save it here since it's been stringified already
|
|
272
274
|
// Params claimed by the pattern shouldn't be repeated in the query string
|
|
273
|
-
focusedParams = {};
|
|
275
|
+
focusedParams = { __proto__: null };
|
|
274
276
|
|
|
275
277
|
for (const key in currentParams) {
|
|
276
278
|
const value = currentParams[key];
|
|
@@ -405,7 +407,7 @@ export function getPathFromState<ParamList extends {}>(
|
|
|
405
407
|
}
|
|
406
408
|
|
|
407
409
|
if (!focusedParams && focusedRoute.params) {
|
|
408
|
-
focusedParams = {};
|
|
410
|
+
focusedParams = { __proto__: null };
|
|
409
411
|
|
|
410
412
|
const params = focusedRoute.params as Record<string, unknown>;
|
|
411
413
|
|
|
@@ -421,7 +423,7 @@ export function getPathFromState<ParamList extends {}>(
|
|
|
421
423
|
if (routeState) {
|
|
422
424
|
path += '/';
|
|
423
425
|
} else if (focusedParams) {
|
|
424
|
-
const query = queryString.stringify(focusedParams
|
|
426
|
+
const query = queryString.stringify(focusedParams);
|
|
425
427
|
|
|
426
428
|
if (query) {
|
|
427
429
|
path += `?${query}`;
|
package/src/getStateFromPath.tsx
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { NavigationState, PartialState } from '@react-navigation/routers';
|
|
2
|
-
import queryString from 'query-string';
|
|
3
2
|
|
|
4
3
|
import { arrayStartsWith } from './arrayStartsWith';
|
|
5
4
|
import {
|
|
@@ -8,6 +7,7 @@ import {
|
|
|
8
7
|
type PatternPartRepeat,
|
|
9
8
|
} from './getPatternParts';
|
|
10
9
|
import { isArrayEqual } from './isArrayEqual';
|
|
10
|
+
import * as queryString from './queryString';
|
|
11
11
|
import type { PathConfig, PathConfigMap } from './types';
|
|
12
12
|
import type {
|
|
13
13
|
StandardSchemaV1,
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
type QueryValue = string | null | (string | null)[];
|
|
2
|
+
|
|
3
|
+
// Match complete UTF-8 sequences so malformed input can be decoded partially
|
|
4
|
+
// without repeatedly splitting and retrying an arbitrarily long byte sequence.
|
|
5
|
+
const ENCODED_UTF8 = new RegExp(
|
|
6
|
+
[
|
|
7
|
+
'%[0-7][\\dA-F]',
|
|
8
|
+
'%(?:C[2-9A-F]|D[\\dA-F])%[89AB][\\dA-F]',
|
|
9
|
+
'%E0%[AB][\\dA-F]%[89AB][\\dA-F]',
|
|
10
|
+
'%E[1-9A-CEF](?:%[89AB][\\dA-F]){2}',
|
|
11
|
+
'%ED%[89][\\dA-F]%[89AB][\\dA-F]',
|
|
12
|
+
'%F0%[9AB][\\dA-F](?:%[89AB][\\dA-F]){2}',
|
|
13
|
+
'%F[1-3](?:%[89AB][\\dA-F]){3}',
|
|
14
|
+
'%F4%8[\\dA-F](?:%[89AB][\\dA-F]){2}',
|
|
15
|
+
].join('|'),
|
|
16
|
+
'gi'
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
const decode = (value: string) => {
|
|
20
|
+
if (!value.includes('%')) {
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
return decodeURIComponent(value);
|
|
26
|
+
} catch {
|
|
27
|
+
return value.replace(ENCODED_UTF8, (sequence) =>
|
|
28
|
+
decodeURIComponent(sequence)
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const encode = (value: string) => {
|
|
34
|
+
if (/^[A-Za-z0-9_.~-]*$/.test(value)) {
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return encodeURIComponent(value).replace(
|
|
39
|
+
/[!'()*]/g,
|
|
40
|
+
(char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`
|
|
41
|
+
);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export function parse(query: string): Record<string, QueryValue> {
|
|
45
|
+
const params: Record<string, QueryValue> = Object.create(null);
|
|
46
|
+
|
|
47
|
+
query = query.trim().replace(/^[?#&]/, '');
|
|
48
|
+
|
|
49
|
+
let start = 0;
|
|
50
|
+
|
|
51
|
+
while (start < query.length) {
|
|
52
|
+
if (query[start] === '&') {
|
|
53
|
+
start++;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const end = query.indexOf('&', start);
|
|
58
|
+
const index = end === -1 ? query.length : end;
|
|
59
|
+
const part = query.slice(start, index).replace(/\+/g, ' ');
|
|
60
|
+
const separator = part.indexOf('=');
|
|
61
|
+
const key = decode(separator === -1 ? part : part.slice(0, separator));
|
|
62
|
+
const value = separator === -1 ? null : decode(part.slice(separator + 1));
|
|
63
|
+
const previous = params[key];
|
|
64
|
+
|
|
65
|
+
if (previous === undefined) {
|
|
66
|
+
params[key] = value;
|
|
67
|
+
} else if (Array.isArray(previous)) {
|
|
68
|
+
previous.push(value);
|
|
69
|
+
} else {
|
|
70
|
+
params[key] = [previous, value];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
start = index + 1;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return params;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function stringify(
|
|
80
|
+
params: Record<string, string | string[] | null>
|
|
81
|
+
): string {
|
|
82
|
+
const parts: string[] = [];
|
|
83
|
+
|
|
84
|
+
for (const [key, value] of Object.entries(params)) {
|
|
85
|
+
if (Array.isArray(value)) {
|
|
86
|
+
let encodedKey: string | undefined;
|
|
87
|
+
|
|
88
|
+
value.forEach((item) => {
|
|
89
|
+
encodedKey ??= encode(key);
|
|
90
|
+
|
|
91
|
+
parts.push(`${encodedKey}=${encode(item)}`);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const encodedKey = encode(key);
|
|
98
|
+
|
|
99
|
+
if (value === null) {
|
|
100
|
+
if (encodedKey) {
|
|
101
|
+
parts.push(encodedKey);
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
parts.push(`${encodedKey}=${encode(value)}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return parts.join('&');
|
|
109
|
+
}
|
package/src/useDescriptors.tsx
CHANGED
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
Router,
|
|
7
7
|
} from '@react-navigation/routers';
|
|
8
8
|
import * as React from 'react';
|
|
9
|
+
import useLatestCallback from 'use-latest-callback';
|
|
9
10
|
|
|
10
11
|
import {
|
|
11
12
|
type AddKeyedListener,
|
|
@@ -68,11 +69,11 @@ type Options<
|
|
|
68
69
|
ScreenOptions extends {},
|
|
69
70
|
EventMap extends EventMapBase,
|
|
70
71
|
> = {
|
|
71
|
-
routes: State['routes'];
|
|
72
72
|
screens: Record<
|
|
73
73
|
string,
|
|
74
74
|
ScreenConfigWithParent<State, ScreenOptions, EventMap>
|
|
75
75
|
>;
|
|
76
|
+
state: State;
|
|
76
77
|
navigation: NavigationHelpers<ParamListBase>;
|
|
77
78
|
screenOptions: ScreenOptionsOrCallback<ScreenOptions> | undefined;
|
|
78
79
|
screenLayout: ScreenLayout<ScreenOptions> | undefined;
|
|
@@ -101,8 +102,8 @@ export function useDescriptors<
|
|
|
101
102
|
ScreenOptions extends {},
|
|
102
103
|
EventMap extends EventMapBase,
|
|
103
104
|
>({
|
|
104
|
-
routes,
|
|
105
105
|
screens,
|
|
106
|
+
state,
|
|
106
107
|
navigation,
|
|
107
108
|
screenOptions,
|
|
108
109
|
screenLayout,
|
|
@@ -117,6 +118,7 @@ export function useDescriptors<
|
|
|
117
118
|
emitter,
|
|
118
119
|
}: Options<State, ScreenOptions, EventMap>) {
|
|
119
120
|
const theme = React.use(ThemeContext);
|
|
121
|
+
|
|
120
122
|
const [options, setOptions] = React.useState<
|
|
121
123
|
Record<string, Partial<ScreenOptions> | undefined>
|
|
122
124
|
>({});
|
|
@@ -167,7 +169,7 @@ export function useDescriptors<
|
|
|
167
169
|
EventMap,
|
|
168
170
|
ActionHelpers
|
|
169
171
|
>({
|
|
170
|
-
routes,
|
|
172
|
+
routes: state.routes,
|
|
171
173
|
getState,
|
|
172
174
|
navigation,
|
|
173
175
|
setOptions,
|
|
@@ -175,7 +177,11 @@ export function useDescriptors<
|
|
|
175
177
|
emitter,
|
|
176
178
|
});
|
|
177
179
|
|
|
178
|
-
const cachedRoutes = useRouteCache(routes);
|
|
180
|
+
const cachedRoutes = useRouteCache(state.routes);
|
|
181
|
+
|
|
182
|
+
const getFocusedRouteKey = useLatestCallback(
|
|
183
|
+
() => state.routes[state.index]?.key
|
|
184
|
+
);
|
|
179
185
|
|
|
180
186
|
const getOptions = (
|
|
181
187
|
route: RouteProp<ParamListBase, string>,
|
|
@@ -258,6 +264,7 @@ export function useDescriptors<
|
|
|
258
264
|
route={route}
|
|
259
265
|
screen={screen}
|
|
260
266
|
routeState={routeState}
|
|
267
|
+
getFocusedRouteKey={getFocusedRouteKey}
|
|
261
268
|
getState={getState}
|
|
262
269
|
setState={setState}
|
|
263
270
|
subscribe={subscribe}
|
|
@@ -313,7 +320,13 @@ export function useDescriptors<
|
|
|
313
320
|
}
|
|
314
321
|
|
|
315
322
|
const customOptions = getOptions(route, navigation, overrides);
|
|
316
|
-
|
|
323
|
+
|
|
324
|
+
const element = render(
|
|
325
|
+
route,
|
|
326
|
+
navigation,
|
|
327
|
+
customOptions,
|
|
328
|
+
state.routes[i]?.state
|
|
329
|
+
);
|
|
317
330
|
|
|
318
331
|
acc[route.key] = {
|
|
319
332
|
route,
|
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
|
}
|
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.
|
|
@@ -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';
|
|
@@ -462,16 +461,20 @@ export function useNavigationBuilder<
|
|
|
462
461
|
|
|
463
462
|
const { onEmitEvent, getIsStateEmitted } = useNavigationBuilderContext();
|
|
464
463
|
|
|
465
|
-
const
|
|
466
|
-
|
|
464
|
+
const mountStateRef = React.useRef<'initial' | 'mounted' | 'unmounted'>(
|
|
465
|
+
'initial'
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
const previousStateRef = React.useRef(currentState);
|
|
469
|
+
const savedStateRef = React.useRef<State | PartialState<State> | undefined>(
|
|
467
470
|
undefined
|
|
468
471
|
);
|
|
469
472
|
|
|
470
473
|
const setState = useLatestCallback(
|
|
471
474
|
(state: State | PartialState<State> | undefined) => {
|
|
472
|
-
if (
|
|
475
|
+
if (mountStateRef.current === 'unmounted') {
|
|
473
476
|
// Store the state locally in case the current navigator is in `Activity`
|
|
474
|
-
|
|
477
|
+
savedStateRef.current = state;
|
|
475
478
|
|
|
476
479
|
// State might have been already cleaned up due to unmount
|
|
477
480
|
// We don't want to update `route.state` in parent
|
|
@@ -483,6 +486,19 @@ export function useNavigationBuilder<
|
|
|
483
486
|
}
|
|
484
487
|
);
|
|
485
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
|
+
|
|
486
502
|
const [
|
|
487
503
|
stateBeforeInitialization,
|
|
488
504
|
initializedState,
|
|
@@ -494,17 +510,10 @@ export function useNavigationBuilder<
|
|
|
494
510
|
boolean,
|
|
495
511
|
object | undefined,
|
|
496
512
|
] => {
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
stateCleanupRef.current &&
|
|
502
|
-
lastStateRef.current &&
|
|
503
|
-
isStateValid(lastStateRef.current)
|
|
504
|
-
) {
|
|
505
|
-
const state: State = isStateInitialized(lastStateRef.current)
|
|
506
|
-
? lastStateRef.current
|
|
507
|
-
: router.getRehydratedState(lastStateRef.current, {
|
|
513
|
+
if (lastState && isStateValid(lastState)) {
|
|
514
|
+
const state: State = isStateInitialized(lastState)
|
|
515
|
+
? lastState
|
|
516
|
+
: router.getRehydratedState(lastState, {
|
|
508
517
|
routeNames,
|
|
509
518
|
routeParamList,
|
|
510
519
|
routeGetIdList,
|
|
@@ -605,7 +614,7 @@ export function useNavigationBuilder<
|
|
|
605
614
|
// that some changes to routeConfigs are explicitly ignored, such as changes
|
|
606
615
|
// to initialParams
|
|
607
616
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
608
|
-
}, [currentState, router, isStateValid]);
|
|
617
|
+
}, [currentState, lastState, router, isStateValid]);
|
|
609
618
|
|
|
610
619
|
const previousRouteKeyListRef = React.useRef(routeKeyList);
|
|
611
620
|
|
|
@@ -645,7 +654,7 @@ export function useNavigationBuilder<
|
|
|
645
654
|
// If the state isn't initialized, or stale, use the state we initialized instead
|
|
646
655
|
// The state won't update until there's a change needed in the state we have initialized locally
|
|
647
656
|
// So it'll be `undefined` or stale until the first navigation event happens
|
|
648
|
-
isStateInitialized(currentState)
|
|
657
|
+
lastState === undefined && isStateInitialized(currentState)
|
|
649
658
|
? (currentState as State)
|
|
650
659
|
: (initializedState as State);
|
|
651
660
|
|
|
@@ -770,7 +779,7 @@ export function useNavigationBuilder<
|
|
|
770
779
|
: nextState;
|
|
771
780
|
}
|
|
772
781
|
|
|
773
|
-
React.
|
|
782
|
+
React.useInsertionEffect(() => {
|
|
774
783
|
if (
|
|
775
784
|
consumedParams &&
|
|
776
785
|
didConsumeNestedParams &&
|
|
@@ -799,24 +808,29 @@ export function useNavigationBuilder<
|
|
|
799
808
|
// So we override the state object we return to use the latest state as soon as possible
|
|
800
809
|
state = nextState;
|
|
801
810
|
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
811
|
+
React.useInsertionEffect(() => {
|
|
812
|
+
savedStateRef.current = state;
|
|
813
|
+
previousStateRef.current = currentState;
|
|
805
814
|
});
|
|
806
815
|
|
|
807
|
-
|
|
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
|
+
}, []);
|
|
808
823
|
|
|
809
824
|
React.useEffect(() => {
|
|
825
|
+
const savedState = savedStateRef.current ?? state;
|
|
826
|
+
|
|
810
827
|
// In strict mode, React will double-invoke effects.
|
|
811
|
-
// So we need to reset the
|
|
812
|
-
|
|
828
|
+
// So we need to reset the status if component was not unmounted
|
|
829
|
+
mountStateRef.current = 'mounted';
|
|
813
830
|
|
|
814
831
|
setKey(navigatorKey);
|
|
815
832
|
|
|
816
|
-
if (
|
|
817
|
-
(!getIsInitial() || getIsStateEmitted()) &&
|
|
818
|
-
lastNotifiedStateRef.current !== state
|
|
819
|
-
) {
|
|
833
|
+
if (!getIsInitial() || getIsStateEmitted()) {
|
|
820
834
|
// We need to notify the state update in these scenarios:
|
|
821
835
|
// 1. If it's not the initial render of the component containing the navigator
|
|
822
836
|
// 2. If the container has already emitted state before the navigator mounted
|
|
@@ -829,31 +843,25 @@ export function useNavigationBuilder<
|
|
|
829
843
|
// So only checking for the initial render is not enough.
|
|
830
844
|
// We also need to propagate the navigator's initial state when it mounts later,
|
|
831
845
|
// so the container is notified that the nested state is now available.
|
|
832
|
-
|
|
833
|
-
// We only notify if the state is different from what we already notified
|
|
834
|
-
// Otherwise, this goes into a loop when inside `<Activity mode="hidden">`
|
|
835
|
-
setState(state);
|
|
836
|
-
|
|
837
|
-
lastNotifiedStateRef.current = state;
|
|
846
|
+
setState(savedState);
|
|
838
847
|
}
|
|
839
848
|
|
|
840
849
|
return () => {
|
|
841
850
|
// We need to clean up state for this navigator on unmount
|
|
842
851
|
if (getCurrentState() !== undefined && getKey() === navigatorKey) {
|
|
843
852
|
setCurrentState(undefined);
|
|
844
|
-
stateCleanupRef.current = true;
|
|
845
853
|
}
|
|
846
854
|
|
|
847
|
-
|
|
848
|
-
// Without this, the guard above sees the same reference and skips setState,
|
|
849
|
-
// causing the initial state to be lost after cleanup wipes the container state.
|
|
850
|
-
lastNotifiedStateRef.current = null;
|
|
855
|
+
mountStateRef.current = 'unmounted';
|
|
851
856
|
};
|
|
852
857
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
853
858
|
}, []);
|
|
854
859
|
|
|
855
860
|
const getState = useLatestCallback((): State => {
|
|
856
|
-
const currentState =
|
|
861
|
+
const currentState =
|
|
862
|
+
mountStateRef.current === 'unmounted'
|
|
863
|
+
? savedStateRef.current
|
|
864
|
+
: getCurrentState();
|
|
857
865
|
|
|
858
866
|
return deepFreeze(
|
|
859
867
|
(isStateInitialized(currentState)
|
|
@@ -1026,8 +1034,8 @@ export function useNavigationBuilder<
|
|
|
1026
1034
|
ScreenOptions,
|
|
1027
1035
|
EventMap
|
|
1028
1036
|
>({
|
|
1029
|
-
routes: state.routes,
|
|
1030
1037
|
screens,
|
|
1038
|
+
state,
|
|
1031
1039
|
navigation,
|
|
1032
1040
|
screenOptions,
|
|
1033
1041
|
screenLayout,
|
|
@@ -1043,7 +1051,7 @@ export function useNavigationBuilder<
|
|
|
1043
1051
|
emitter,
|
|
1044
1052
|
});
|
|
1045
1053
|
|
|
1046
|
-
const
|
|
1054
|
+
const render = (children: React.ReactNode) => {
|
|
1047
1055
|
const focusedRoute = state.routes[state.index];
|
|
1048
1056
|
|
|
1049
1057
|
if (focusedRoute == null) {
|
|
@@ -1080,12 +1088,12 @@ export function useNavigationBuilder<
|
|
|
1080
1088
|
</NavigationHelpersContext.Provider>
|
|
1081
1089
|
</NavigationMetaContext.Provider>
|
|
1082
1090
|
);
|
|
1083
|
-
}
|
|
1091
|
+
};
|
|
1084
1092
|
|
|
1085
1093
|
return {
|
|
1086
1094
|
state,
|
|
1087
1095
|
navigation,
|
|
1088
1096
|
descriptors,
|
|
1089
|
-
|
|
1097
|
+
render,
|
|
1090
1098
|
};
|
|
1091
1099
|
}
|