mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-05-13 10:16:50 +08:00
feat: allow deep linking to reset state (#8973)
Currently when we receive a deep link after the app is rendered, it always results in a `navigate` action. While it's ok with the default configuration, it may result in incorrect behaviour when a custom `getStateForPath` function is provided and it returns a routes array different than the initial route and new route pair. The commit changes 2 things: 1. Add ability to reset state via params of `navigate` by specifying a `state` property instead of `screen` 2. Update `getStateForAction` to return an action for reset when necessary according to the deep linking configuration Closes #8952
This commit is contained in:
@@ -43,32 +43,207 @@ it('gets navigate action from state', () => {
|
||||
},
|
||||
type: 'NAVIGATE',
|
||||
});
|
||||
});
|
||||
|
||||
expect(
|
||||
getActionFromState({
|
||||
routes: [
|
||||
{
|
||||
name: 'foo',
|
||||
it('gets navigate action from state with 2 screens', () => {
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'foo',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'bar',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'qux',
|
||||
params: { author: 'jane' },
|
||||
},
|
||||
{ name: 'quz' },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(getActionFromState(state)).toEqual({
|
||||
payload: {
|
||||
name: 'foo',
|
||||
params: {
|
||||
screen: 'bar',
|
||||
initial: true,
|
||||
params: {
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'bar',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'qux',
|
||||
params: { author: 'jane' },
|
||||
},
|
||||
{ name: 'quz' },
|
||||
],
|
||||
name: 'qux',
|
||||
params: {
|
||||
author: 'jane',
|
||||
},
|
||||
},
|
||||
{ name: 'quz' },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toEqual({
|
||||
},
|
||||
},
|
||||
type: 'NAVIGATE',
|
||||
});
|
||||
});
|
||||
|
||||
it('gets navigate action from state with more than 2 screens', () => {
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'foo',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'bar',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'qux',
|
||||
params: { author: 'jane' },
|
||||
},
|
||||
{ name: 'quz' },
|
||||
{ name: 'qua' },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(getActionFromState(state)).toEqual({
|
||||
payload: {
|
||||
name: 'foo',
|
||||
params: {
|
||||
screen: 'bar',
|
||||
initial: true,
|
||||
params: {
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'qux',
|
||||
params: {
|
||||
author: 'jane',
|
||||
},
|
||||
},
|
||||
{ name: 'quz' },
|
||||
{ name: 'qua' },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
type: 'NAVIGATE',
|
||||
});
|
||||
});
|
||||
|
||||
it('gets navigate action from state with config', () => {
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'foo',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'bar',
|
||||
params: { answer: 42 },
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'qux',
|
||||
params: { author: 'jane' },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const config = {
|
||||
screens: {
|
||||
foo: {
|
||||
initialRouteName: 'bar',
|
||||
screens: {
|
||||
bar: {
|
||||
initialRouteName: 'qux',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(getActionFromState(state, config)).toEqual({
|
||||
payload: {
|
||||
name: 'foo',
|
||||
params: {
|
||||
params: {
|
||||
answer: 42,
|
||||
params: {
|
||||
author: 'jane',
|
||||
},
|
||||
screen: 'qux',
|
||||
initial: true,
|
||||
},
|
||||
screen: 'bar',
|
||||
initial: true,
|
||||
},
|
||||
},
|
||||
type: 'NAVIGATE',
|
||||
});
|
||||
});
|
||||
|
||||
it('gets navigate action from state with 2 screens including initial route and with config', () => {
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'foo',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'bar',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'qux',
|
||||
params: { author: 'jane' },
|
||||
},
|
||||
{ name: 'quz' },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const config = {
|
||||
screens: {
|
||||
foo: {
|
||||
initialRouteName: 'bar',
|
||||
screens: {
|
||||
bar: {
|
||||
initialRouteName: 'qux',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(getActionFromState(state, config)).toEqual({
|
||||
payload: {
|
||||
name: 'foo',
|
||||
params: {
|
||||
@@ -84,6 +259,203 @@ it('gets navigate action from state', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('gets navigate action from state with 2 screens without initial route and with config', () => {
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'foo',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'bar',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'qux',
|
||||
params: { author: 'jane' },
|
||||
},
|
||||
{ name: 'quz' },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const config = {
|
||||
screens: {
|
||||
foo: {
|
||||
initialRouteName: 'bar',
|
||||
screens: {
|
||||
bar: {
|
||||
initialRouteName: 'quz',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(getActionFromState(state, config)).toEqual({
|
||||
payload: {
|
||||
name: 'foo',
|
||||
params: {
|
||||
initial: true,
|
||||
screen: 'bar',
|
||||
params: {
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'qux',
|
||||
params: {
|
||||
author: 'jane',
|
||||
},
|
||||
},
|
||||
{ name: 'quz' },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
type: 'NAVIGATE',
|
||||
});
|
||||
});
|
||||
|
||||
it('gets navigate action from state with 2 screens including route with key and with config', () => {
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'foo',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'bar',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
key: 'test',
|
||||
name: 'qux',
|
||||
params: { author: 'jane' },
|
||||
},
|
||||
{ name: 'quz' },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const config = {
|
||||
screens: {
|
||||
foo: {
|
||||
initialRouteName: 'bar',
|
||||
screens: {
|
||||
bar: {
|
||||
initialRouteName: 'qux',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(getActionFromState(state, config)).toEqual({
|
||||
payload: {
|
||||
name: 'foo',
|
||||
params: {
|
||||
initial: true,
|
||||
screen: 'bar',
|
||||
params: {
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
key: 'test',
|
||||
name: 'qux',
|
||||
params: {
|
||||
author: 'jane',
|
||||
},
|
||||
},
|
||||
{ name: 'quz' },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
type: 'NAVIGATE',
|
||||
});
|
||||
});
|
||||
|
||||
it('gets navigate action from state with more than 2 screens and with config', () => {
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'foo',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'bar',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'qux',
|
||||
params: { author: 'jane' },
|
||||
},
|
||||
{ name: 'quz' },
|
||||
{ name: 'qua' },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const config = {
|
||||
screens: {
|
||||
foo: {
|
||||
initialRouteName: 'bar',
|
||||
screens: {
|
||||
bar: {
|
||||
initialRouteName: 'qux',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(getActionFromState(state, config)).toEqual({
|
||||
payload: {
|
||||
name: 'foo',
|
||||
params: {
|
||||
initial: true,
|
||||
screen: 'bar',
|
||||
params: {
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'qux',
|
||||
params: {
|
||||
author: 'jane',
|
||||
},
|
||||
},
|
||||
{ name: 'quz' },
|
||||
{ name: 'qua' },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
type: 'NAVIGATE',
|
||||
});
|
||||
});
|
||||
|
||||
it("doesn't return action if no routes are provided'", () => {
|
||||
expect(getActionFromState({ routes: [] })).toBe(undefined);
|
||||
});
|
||||
|
||||
it('gets reset action from state', () => {
|
||||
const state = {
|
||||
routes: [
|
||||
|
||||
@@ -1093,6 +1093,194 @@ it('navigates to nested child in a navigator with initial: false', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('resets state of a nested child in a navigator', () => {
|
||||
const TestNavigator = (props: any): any => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
return descriptors[state.routes[state.index].key].render();
|
||||
};
|
||||
|
||||
const TestComponent = ({ route }: any): any =>
|
||||
`[${route.name}, ${JSON.stringify(route.params)}]`;
|
||||
|
||||
const onStateChange = jest.fn();
|
||||
|
||||
const navigation = React.createRef<NavigationContainerRef>();
|
||||
|
||||
const first = render(
|
||||
<BaseNavigationContainer ref={navigation} onStateChange={onStateChange}>
|
||||
<TestNavigator>
|
||||
<Screen name="foo">
|
||||
{() => (
|
||||
<TestNavigator>
|
||||
<Screen name="foo-a" component={TestComponent} />
|
||||
<Screen name="foo-b" component={TestComponent} />
|
||||
</TestNavigator>
|
||||
)}
|
||||
</Screen>
|
||||
<Screen name="bar">
|
||||
{() => (
|
||||
<TestNavigator initialRouteName="bar-a">
|
||||
<Screen name="bar-a" component={TestComponent} />
|
||||
<Screen
|
||||
name="bar-b"
|
||||
component={TestComponent}
|
||||
initialParams={{ some: 'stuff' }}
|
||||
/>
|
||||
</TestNavigator>
|
||||
)}
|
||||
</Screen>
|
||||
</TestNavigator>
|
||||
</BaseNavigationContainer>
|
||||
);
|
||||
|
||||
expect(first).toMatchInlineSnapshot(`"[foo-a, undefined]"`);
|
||||
|
||||
expect(navigation.current?.getRootState()).toEqual({
|
||||
index: 0,
|
||||
key: '0',
|
||||
routeNames: ['foo', 'bar'],
|
||||
routes: [
|
||||
{
|
||||
key: 'foo',
|
||||
name: 'foo',
|
||||
state: {
|
||||
index: 0,
|
||||
key: '1',
|
||||
routeNames: ['foo-a', 'foo-b'],
|
||||
routes: [
|
||||
{
|
||||
key: 'foo-a',
|
||||
name: 'foo-a',
|
||||
},
|
||||
{
|
||||
key: 'foo-b',
|
||||
name: 'foo-b',
|
||||
},
|
||||
],
|
||||
stale: false,
|
||||
type: 'test',
|
||||
},
|
||||
},
|
||||
{ key: 'bar', name: 'bar' },
|
||||
],
|
||||
stale: false,
|
||||
type: 'test',
|
||||
});
|
||||
|
||||
act(() =>
|
||||
navigation.current?.navigate('bar', {
|
||||
state: {
|
||||
routes: [{ name: 'bar-a' }, { name: 'bar-b' }],
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
expect(first).toMatchInlineSnapshot(`"[bar-a, undefined]"`);
|
||||
|
||||
expect(navigation.current?.getRootState()).toEqual({
|
||||
index: 1,
|
||||
key: '0',
|
||||
routeNames: ['foo', 'bar'],
|
||||
routes: [
|
||||
{ key: 'foo', name: 'foo' },
|
||||
{
|
||||
key: 'bar',
|
||||
name: 'bar',
|
||||
params: {
|
||||
state: {
|
||||
routes: [{ name: 'bar-a' }, { name: 'bar-b' }],
|
||||
},
|
||||
},
|
||||
state: {
|
||||
index: 0,
|
||||
key: '4',
|
||||
routeNames: ['bar-a', 'bar-b'],
|
||||
routes: [
|
||||
{
|
||||
key: 'bar-a-2',
|
||||
name: 'bar-a',
|
||||
},
|
||||
{
|
||||
key: 'bar-b-3',
|
||||
name: 'bar-b',
|
||||
params: { some: 'stuff' },
|
||||
},
|
||||
],
|
||||
stale: false,
|
||||
type: 'test',
|
||||
},
|
||||
},
|
||||
],
|
||||
stale: false,
|
||||
type: 'test',
|
||||
});
|
||||
|
||||
act(() =>
|
||||
navigation.current?.navigate('bar', {
|
||||
state: {
|
||||
index: 2,
|
||||
routes: [
|
||||
{ key: '37', name: 'bar-b' },
|
||||
{ name: 'bar-b' },
|
||||
{ name: 'bar-a', params: { test: 18 } },
|
||||
],
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
expect(first).toMatchInlineSnapshot(`"[bar-a, {\\"test\\":18}]"`);
|
||||
|
||||
expect(navigation.current?.getRootState()).toEqual({
|
||||
index: 1,
|
||||
key: '0',
|
||||
routeNames: ['foo', 'bar'],
|
||||
routes: [
|
||||
{ key: 'foo', name: 'foo' },
|
||||
{
|
||||
key: 'bar',
|
||||
name: 'bar',
|
||||
params: {
|
||||
state: {
|
||||
index: 2,
|
||||
routes: [
|
||||
{ key: '37', name: 'bar-b' },
|
||||
{ name: 'bar-b' },
|
||||
{ name: 'bar-a', params: { test: 18 } },
|
||||
],
|
||||
},
|
||||
},
|
||||
state: {
|
||||
index: 2,
|
||||
key: '7',
|
||||
routeNames: ['bar-a', 'bar-b'],
|
||||
routes: [
|
||||
{
|
||||
key: '37',
|
||||
name: 'bar-b',
|
||||
params: { some: 'stuff' },
|
||||
},
|
||||
{
|
||||
key: 'bar-b-5',
|
||||
name: 'bar-b',
|
||||
params: { some: 'stuff' },
|
||||
},
|
||||
{
|
||||
key: 'bar-a-6',
|
||||
name: 'bar-a',
|
||||
params: { test: 18 },
|
||||
},
|
||||
],
|
||||
stale: false,
|
||||
type: 'test',
|
||||
},
|
||||
},
|
||||
],
|
||||
stale: false,
|
||||
type: 'test',
|
||||
});
|
||||
});
|
||||
|
||||
it('gives access to internal state', () => {
|
||||
const TestNavigator = (props: any): any => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
@@ -1,43 +1,65 @@
|
||||
import type { PartialState, NavigationState } from '@react-navigation/routers';
|
||||
import type {
|
||||
Route,
|
||||
PartialRoute,
|
||||
NavigationState,
|
||||
PartialState,
|
||||
} from '@react-navigation/routers';
|
||||
import type { PathConfig, PathConfigMap, NestedNavigateParams } from './types';
|
||||
|
||||
type NavigateParams = {
|
||||
screen?: string;
|
||||
params?: NavigateParams;
|
||||
initial?: boolean;
|
||||
type ConfigItem = {
|
||||
initialRouteName?: string;
|
||||
screens?: Record<string, ConfigItem>;
|
||||
};
|
||||
|
||||
type NavigateAction = {
|
||||
type Options = { initialRouteName?: string; screens: PathConfigMap };
|
||||
|
||||
type NavigateAction<State extends NavigationState> = {
|
||||
type: 'NAVIGATE';
|
||||
payload: { name: string; params: NavigateParams };
|
||||
payload: {
|
||||
name: string;
|
||||
params?: NestedNavigateParams<State>;
|
||||
};
|
||||
};
|
||||
|
||||
export default function getActionFromState(
|
||||
state: PartialState<NavigationState>
|
||||
): NavigateAction | undefined {
|
||||
if (state.routes.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
state: PartialState<NavigationState>,
|
||||
options?: Options
|
||||
): NavigateAction<NavigationState> | undefined {
|
||||
// Create a normalized configs object which will be easier to use
|
||||
const normalizedConfig = options ? createNormalizedConfigItem(options) : {};
|
||||
|
||||
// Try to construct payload for a `NAVIGATE` action from the state
|
||||
// This lets us preserve the navigation state and not lose it
|
||||
let route = state.routes[state.routes.length - 1];
|
||||
|
||||
let payload: { name: string; params: NavigateParams } = {
|
||||
name: route.name,
|
||||
params: { ...route.params },
|
||||
};
|
||||
|
||||
let current = route.state;
|
||||
let params = payload.params;
|
||||
let payload;
|
||||
let current: PartialState<NavigationState> | undefined = state;
|
||||
let config: ConfigItem | undefined = normalizedConfig;
|
||||
let params: NestedNavigateParams<NavigationState> = {};
|
||||
|
||||
while (current) {
|
||||
if (current.routes.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
route = current.routes[current.routes.length - 1];
|
||||
params.initial = current.routes.length === 1;
|
||||
params.screen = route.name;
|
||||
const route: Route<string> | PartialRoute<Route<string>> =
|
||||
current.routes[current.routes.length - 1];
|
||||
|
||||
if (current.routes.length === 1) {
|
||||
params.initial = true;
|
||||
params.screen = route.name;
|
||||
params.state = undefined; // Explicitly set to override existing value when merging params
|
||||
} else if (
|
||||
current.routes.length === 2 &&
|
||||
current.routes[0].key === undefined &&
|
||||
current.routes[0].name === config?.initialRouteName
|
||||
) {
|
||||
params.initial = false;
|
||||
params.screen = route.name;
|
||||
params.state = undefined;
|
||||
} else {
|
||||
params.initial = undefined;
|
||||
params.screen = undefined;
|
||||
params.params = undefined;
|
||||
params.state = current;
|
||||
break;
|
||||
}
|
||||
|
||||
if (route.state) {
|
||||
params.params = { ...route.params };
|
||||
@@ -47,10 +69,41 @@ export default function getActionFromState(
|
||||
}
|
||||
|
||||
current = route.state;
|
||||
config = config?.screens?.[route.name];
|
||||
|
||||
if (!payload) {
|
||||
payload = {
|
||||
name: route.name,
|
||||
params,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (!payload) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to construct payload for a `NAVIGATE` action from the state
|
||||
// This lets us preserve the navigation state and not lose it
|
||||
return {
|
||||
type: 'NAVIGATE',
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
const createNormalizedConfigItem = (config: PathConfig | string) =>
|
||||
typeof config === 'object' && config != null
|
||||
? {
|
||||
initialRouteName: config.initialRouteName,
|
||||
screens:
|
||||
config.screens != null
|
||||
? createNormalizedConfigs(config.screens)
|
||||
: undefined,
|
||||
}
|
||||
: {};
|
||||
|
||||
const createNormalizedConfigs = (options: PathConfigMap) =>
|
||||
Object.entries(options).reduce<Record<string, ConfigItem>>((acc, [k, v]) => {
|
||||
acc[k] = createNormalizedConfigItem(v);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
@@ -506,6 +506,20 @@ export type TypedNavigator<
|
||||
) => null;
|
||||
};
|
||||
|
||||
export type NestedNavigateParams<State extends NavigationState> =
|
||||
| {
|
||||
screen?: string;
|
||||
params?: object;
|
||||
initial?: boolean;
|
||||
state?: never;
|
||||
}
|
||||
| {
|
||||
screen?: never;
|
||||
params?: never;
|
||||
initial?: never;
|
||||
state?: PartialState<State> | State;
|
||||
};
|
||||
|
||||
export type PathConfig = {
|
||||
path?: string;
|
||||
exact?: boolean;
|
||||
|
||||
@@ -23,30 +23,27 @@ import useFocusEvents from './useFocusEvents';
|
||||
import useOnRouteFocus from './useOnRouteFocus';
|
||||
import useChildListeners from './useChildListeners';
|
||||
import useFocusedListenersChildrenAdapter from './useFocusedListenersChildrenAdapter';
|
||||
import useKeyedChildListeners from './useKeyedChildListeners';
|
||||
import useOnGetState from './useOnGetState';
|
||||
import useScheduleUpdate from './useScheduleUpdate';
|
||||
import useCurrentRender from './useCurrentRender';
|
||||
import isArrayEqual from './isArrayEqual';
|
||||
import {
|
||||
DefaultNavigatorOptions,
|
||||
RouteConfig,
|
||||
PrivateValueStore,
|
||||
EventMapBase,
|
||||
EventMapCore,
|
||||
NestedNavigateParams,
|
||||
} from './types';
|
||||
import useKeyedChildListeners from './useKeyedChildListeners';
|
||||
import useOnGetState from './useOnGetState';
|
||||
import useScheduleUpdate from './useScheduleUpdate';
|
||||
import useCurrentRender from './useCurrentRender';
|
||||
import isArrayEqual from './isArrayEqual';
|
||||
|
||||
// This is to make TypeScript compiler happy
|
||||
// eslint-disable-next-line babel/no-unused-expressions
|
||||
PrivateValueStore;
|
||||
|
||||
type NavigatorRoute = {
|
||||
type NavigatorRoute<State extends NavigationState> = {
|
||||
key: string;
|
||||
params?: {
|
||||
screen?: string;
|
||||
params?: object;
|
||||
initial?: boolean;
|
||||
};
|
||||
params?: NestedNavigateParams<State>;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -192,20 +189,15 @@ export default function useNavigationBuilder<
|
||||
const navigatorKey = useRegisterNavigator();
|
||||
|
||||
const route = React.useContext(NavigationRouteContext) as
|
||||
| NavigatorRoute
|
||||
| NavigatorRoute<State>
|
||||
| undefined;
|
||||
|
||||
const previousNestedParamsRef = React.useRef(route?.params);
|
||||
|
||||
React.useEffect(() => {
|
||||
previousNestedParamsRef.current = route?.params;
|
||||
}, [route]);
|
||||
|
||||
const { children, ...rest } = options;
|
||||
const { current: router } = React.useRef<Router<State, any>>(
|
||||
createRouter({
|
||||
...((rest as unknown) as RouterOptions),
|
||||
...(route?.params &&
|
||||
route.params.state == null &&
|
||||
route.params.initial !== false &&
|
||||
typeof route.params.screen === 'string'
|
||||
? { initialRouteName: route.params.screen }
|
||||
@@ -240,7 +232,9 @@ export default function useNavigationBuilder<
|
||||
(acc, curr) => {
|
||||
const { initialParams } = screens[curr];
|
||||
const initialParamsFromParams =
|
||||
route?.params?.initial !== false && route?.params?.screen === curr
|
||||
route?.params?.state == null &&
|
||||
route?.params?.initial !== false &&
|
||||
route?.params?.screen === curr
|
||||
? route.params.params
|
||||
: undefined;
|
||||
|
||||
@@ -288,7 +282,10 @@ export default function useNavigationBuilder<
|
||||
// We also need to re-initialize it if the state passed from parent was changed (maybe due to reset)
|
||||
// Otherwise assume that the state was provided as initial state
|
||||
// So we need to rehydrate it to make it usable
|
||||
if (currentState === undefined || !isStateValid(currentState)) {
|
||||
if (
|
||||
(currentState === undefined || !isStateValid(currentState)) &&
|
||||
route?.params?.state == null
|
||||
) {
|
||||
return [
|
||||
router.getInitialState({
|
||||
routeNames,
|
||||
@@ -298,10 +295,13 @@ export default function useNavigationBuilder<
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
router.getRehydratedState(currentState as PartialState<State>, {
|
||||
routeNames,
|
||||
routeParamList,
|
||||
}),
|
||||
router.getRehydratedState(
|
||||
route?.params?.state ?? (currentState as PartialState<State>),
|
||||
{
|
||||
routeNames,
|
||||
routeParamList,
|
||||
}
|
||||
),
|
||||
false,
|
||||
];
|
||||
}
|
||||
@@ -332,21 +332,41 @@ export default function useNavigationBuilder<
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
typeof route?.params?.screen === 'string' &&
|
||||
(route.params !== previousNestedParamsRef.current ||
|
||||
(route.params.initial === false && isFirstStateInitialization))
|
||||
) {
|
||||
// If the route was updated with new name and/or params, we should navigate there
|
||||
const previousNestedParamsRef = React.useRef(route?.params);
|
||||
|
||||
React.useEffect(() => {
|
||||
previousNestedParamsRef.current = route?.params;
|
||||
}, [route?.params]);
|
||||
|
||||
if (route?.params) {
|
||||
const previousParams = previousNestedParamsRef.current;
|
||||
|
||||
let action: CommonActions.Action | undefined;
|
||||
|
||||
if (
|
||||
typeof route.params.state === 'object' &&
|
||||
route.params.state != null &&
|
||||
route.params.state !== previousParams?.state
|
||||
) {
|
||||
// If the route was updated with new state, we should reset to it
|
||||
action = CommonActions.reset(route.params.state);
|
||||
} else if (
|
||||
typeof route.params.screen === 'string' &&
|
||||
((route.params.initial === false && isFirstStateInitialization) ||
|
||||
route.params.screen !== previousParams?.screen ||
|
||||
route.params.params !== previousParams?.params)
|
||||
) {
|
||||
// If the route was updated with new screen name and/or params, we should navigate there
|
||||
action = CommonActions.navigate(route.params.screen, route.params.params);
|
||||
}
|
||||
|
||||
// The update should be limited to current navigator only, so we call the router manually
|
||||
const updatedState = router.getStateForAction(
|
||||
nextState,
|
||||
CommonActions.navigate(route.params.screen, route.params.params),
|
||||
{
|
||||
routeNames,
|
||||
routeParamList,
|
||||
}
|
||||
);
|
||||
const updatedState = action
|
||||
? router.getStateForAction(nextState, action, {
|
||||
routeNames,
|
||||
routeParamList,
|
||||
})
|
||||
: null;
|
||||
|
||||
nextState =
|
||||
updatedState !== null
|
||||
|
||||
@@ -37,7 +37,7 @@ export default function useLinkTo() {
|
||||
root = current;
|
||||
}
|
||||
|
||||
const action = getActionFromState(state);
|
||||
const action = getActionFromState(state, options?.config);
|
||||
|
||||
if (action !== undefined) {
|
||||
root.dispatch(action);
|
||||
|
||||
@@ -111,7 +111,7 @@ export default function useLinking(
|
||||
const state = getStateFromPathRef.current(path, configRef.current);
|
||||
|
||||
if (state) {
|
||||
const action = getActionFromState(state);
|
||||
const action = getActionFromState(state, configRef.current);
|
||||
|
||||
if (action !== undefined) {
|
||||
navigation.dispatch(action);
|
||||
|
||||
@@ -400,7 +400,7 @@ export default function useLinking(
|
||||
// We should only dispatch an action when going forward
|
||||
// Otherwise the action will likely add items to history, which would mess things up
|
||||
if (state && index > previousIndex) {
|
||||
const action = getActionFromState(state);
|
||||
const action = getActionFromState(state, configRef.current);
|
||||
|
||||
if (action !== undefined) {
|
||||
navigation.dispatch(action);
|
||||
|
||||
@@ -50,16 +50,18 @@ export type InitialState = Readonly<
|
||||
}
|
||||
>;
|
||||
|
||||
export type PartialRoute<R extends Route<string>> = Omit<R, 'key'> & {
|
||||
key?: string;
|
||||
state?: PartialState<NavigationState>;
|
||||
};
|
||||
|
||||
export type PartialState<State extends NavigationState> = Partial<
|
||||
Omit<State, 'stale' | 'type' | 'key' | 'routes' | 'routeNames'>
|
||||
> &
|
||||
Readonly<{
|
||||
stale?: true;
|
||||
type?: string;
|
||||
routes: (Omit<Route<string>, 'key'> & {
|
||||
key?: string;
|
||||
state?: InitialState;
|
||||
})[];
|
||||
routes: PartialRoute<Route<string>>[];
|
||||
}>;
|
||||
|
||||
export type Route<
|
||||
|
||||
Reference in New Issue
Block a user