mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-01-12 22:51:18 +08:00
feat: add a way to specify an unique ID for screens
With this, the user will be able to specify a `getId` function for their screens which returns an unique ID to use for the screen:
```js
<Stack.Screen
name="Profile"
component={ProfileScreen}
getId={({ params }) => params.userId}
/>
```
This is an alternative to the `key` option in `navigate` with several advantages:
- Often users specify a key that's dependent on data already in params, such as `userId`. So it's much easier to specify it one place rather than at every call site.
- Users won't need to deal with generating a unique key for routes manually.
- This will work with other actions such as `push`, and not just navigate.
- With this, it'll be possible to have multiple instances of the screen even if you use `navigate`, which may be desirable in many cases (such as profile screens).
This commit is contained in:
@@ -393,6 +393,14 @@ export type RouteConfig<
|
||||
navigation: any;
|
||||
}) => ScreenListeners<State, EventMap>);
|
||||
|
||||
/**
|
||||
* Function to return an unique ID for this screen.
|
||||
* Receives an object with the route params.
|
||||
* For a given screen name, there will always be only one screen corresponding to an ID.
|
||||
* If `undefined` is returned, it acts same as no `getId` being specified.
|
||||
*/
|
||||
getId?: ({ params }: { params: ParamList[RouteName] }) => string | undefined;
|
||||
|
||||
/**
|
||||
* Initial params object for the route.
|
||||
*/
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
ParamListBase,
|
||||
Router,
|
||||
RouterFactory,
|
||||
RouterConfigOptions,
|
||||
PartialState,
|
||||
NavigationAction,
|
||||
Route,
|
||||
@@ -257,6 +258,15 @@ export default function useNavigationBuilder<
|
||||
},
|
||||
{}
|
||||
);
|
||||
const routeGetIdList = routeNames.reduce<
|
||||
RouterConfigOptions['routeGetIdList']
|
||||
>(
|
||||
(acc, curr) =>
|
||||
Object.assign(acc, {
|
||||
[curr]: screens[curr].getId,
|
||||
}),
|
||||
{}
|
||||
);
|
||||
|
||||
if (!routeNames.length) {
|
||||
throw new Error(
|
||||
@@ -297,6 +307,7 @@ export default function useNavigationBuilder<
|
||||
router.getInitialState({
|
||||
routeNames,
|
||||
routeParamList,
|
||||
routeGetIdList,
|
||||
}),
|
||||
true,
|
||||
];
|
||||
@@ -307,6 +318,7 @@ export default function useNavigationBuilder<
|
||||
{
|
||||
routeNames,
|
||||
routeParamList,
|
||||
routeGetIdList,
|
||||
}
|
||||
),
|
||||
false,
|
||||
@@ -336,6 +348,7 @@ export default function useNavigationBuilder<
|
||||
nextState = router.getStateForRouteNamesChange(state, {
|
||||
routeNames,
|
||||
routeParamList,
|
||||
routeGetIdList,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -371,6 +384,7 @@ export default function useNavigationBuilder<
|
||||
? router.getStateForAction(nextState, action, {
|
||||
routeNames,
|
||||
routeParamList,
|
||||
routeGetIdList,
|
||||
})
|
||||
: null;
|
||||
|
||||
@@ -379,6 +393,7 @@ export default function useNavigationBuilder<
|
||||
? router.getRehydratedState(updatedState, {
|
||||
routeNames,
|
||||
routeParamList,
|
||||
routeGetIdList,
|
||||
})
|
||||
: nextState;
|
||||
}
|
||||
@@ -500,6 +515,7 @@ export default function useNavigationBuilder<
|
||||
routerConfigOptions: {
|
||||
routeNames,
|
||||
routeParamList,
|
||||
routeGetIdList,
|
||||
},
|
||||
emitter,
|
||||
});
|
||||
|
||||
@@ -78,6 +78,7 @@ export default function useNavigationHelpers<
|
||||
router.getStateForAction(state, CommonActions.goBack() as Action, {
|
||||
routeNames: state.routeNames,
|
||||
routeParamList: {},
|
||||
routeGetIdList: {},
|
||||
}) !== null ||
|
||||
parentNavigationHelpers?.canGoBack() ||
|
||||
false
|
||||
|
||||
@@ -120,8 +120,12 @@ export default function DrawerRouter({
|
||||
|
||||
type: 'drawer',
|
||||
|
||||
getInitialState({ routeNames, routeParamList }) {
|
||||
let state = router.getInitialState({ routeNames, routeParamList });
|
||||
getInitialState({ routeNames, routeParamList, routeGetIdList }) {
|
||||
let state = router.getInitialState({
|
||||
routeNames,
|
||||
routeParamList,
|
||||
routeGetIdList,
|
||||
});
|
||||
|
||||
if (openByDefault) {
|
||||
state = openDrawer(state);
|
||||
@@ -135,7 +139,10 @@ export default function DrawerRouter({
|
||||
};
|
||||
},
|
||||
|
||||
getRehydratedState(partialState, { routeNames, routeParamList }) {
|
||||
getRehydratedState(
|
||||
partialState,
|
||||
{ routeNames, routeParamList, routeGetIdList }
|
||||
) {
|
||||
if (partialState.stale === false) {
|
||||
return partialState;
|
||||
}
|
||||
@@ -143,6 +150,7 @@ export default function DrawerRouter({
|
||||
let state = router.getRehydratedState(partialState, {
|
||||
routeNames,
|
||||
routeParamList,
|
||||
routeGetIdList,
|
||||
});
|
||||
|
||||
if (isDrawerOpen(partialState)) {
|
||||
|
||||
@@ -18,7 +18,7 @@ export type StackActionType =
|
||||
}
|
||||
| {
|
||||
type: 'PUSH';
|
||||
payload: { name: string; key?: string | undefined; params?: object };
|
||||
payload: { name: string; params?: object };
|
||||
source?: string;
|
||||
target?: string;
|
||||
}
|
||||
@@ -258,41 +258,34 @@ export default function StackRouter(options: StackRouterOptions) {
|
||||
|
||||
case 'PUSH':
|
||||
if (state.routeNames.includes(action.payload.name)) {
|
||||
const route =
|
||||
action.payload.name && action.payload.key
|
||||
? state.routes.find(
|
||||
(route) =>
|
||||
route.name === action.payload.name &&
|
||||
route.key === action.payload.key
|
||||
)
|
||||
: undefined;
|
||||
const getId = options.routeGetIdList[action.payload.name];
|
||||
const id = getId?.({ params: action.payload.params });
|
||||
|
||||
const route = id
|
||||
? state.routes.find(
|
||||
(route) => id === getId?.({ params: route.params })
|
||||
)
|
||||
: undefined;
|
||||
|
||||
let routes: Route<string>[];
|
||||
|
||||
if (route) {
|
||||
routes = state.routes.filter((r) => r.key !== route.key);
|
||||
routes.push(
|
||||
action.payload.params
|
||||
? {
|
||||
...route,
|
||||
params:
|
||||
action.payload.params !== undefined
|
||||
? {
|
||||
...route.params,
|
||||
...action.payload.params,
|
||||
}
|
||||
: route.params,
|
||||
}
|
||||
: route
|
||||
);
|
||||
routes.push({
|
||||
...route,
|
||||
params:
|
||||
routeParamList[action.payload.name] !== undefined
|
||||
? {
|
||||
...routeParamList[action.payload.name],
|
||||
...action.payload.params,
|
||||
}
|
||||
: action.payload.params,
|
||||
});
|
||||
} else {
|
||||
routes = [
|
||||
...state.routes,
|
||||
{
|
||||
key:
|
||||
action.payload.key === undefined
|
||||
? `${action.payload.name}-${nanoid()}`
|
||||
: action.payload.key,
|
||||
key: `${action.payload.name}-${nanoid()}`,
|
||||
name: action.payload.name,
|
||||
params:
|
||||
routeParamList[action.payload.name] !== undefined
|
||||
@@ -355,7 +348,19 @@ export default function StackRouter(options: StackRouterOptions) {
|
||||
// If the route already exists, navigate to that
|
||||
let index = -1;
|
||||
|
||||
if (
|
||||
const getId =
|
||||
// `getId` and `key` can't be used together
|
||||
action.payload.key === undefined &&
|
||||
action.payload.name !== undefined
|
||||
? options.routeGetIdList[action.payload.name]
|
||||
: undefined;
|
||||
const id = getId?.({ params: action.payload.params });
|
||||
|
||||
if (id) {
|
||||
index = state.routes.findIndex(
|
||||
(route) => id === getId?.({ params: route.params })
|
||||
);
|
||||
} else if (
|
||||
(state.routes[state.index].name === action.payload.name &&
|
||||
action.payload.key === undefined) ||
|
||||
state.routes[state.index].key === action.payload.key
|
||||
@@ -383,18 +388,27 @@ export default function StackRouter(options: StackRouterOptions) {
|
||||
}
|
||||
|
||||
if (index === -1 && action.payload.name !== undefined) {
|
||||
return router.getStateForAction(
|
||||
state,
|
||||
const routes = [
|
||||
...state.routes,
|
||||
{
|
||||
type: 'PUSH',
|
||||
payload: {
|
||||
key: action.payload.key,
|
||||
name: action.payload.name,
|
||||
params: action.payload.params,
|
||||
},
|
||||
key:
|
||||
action.payload.key ?? `${action.payload.name}-${nanoid()}`,
|
||||
name: action.payload.name,
|
||||
params:
|
||||
routeParamList[action.payload.name] !== undefined
|
||||
? {
|
||||
...routeParamList[action.payload.name],
|
||||
...action.payload.params,
|
||||
}
|
||||
: action.payload.params,
|
||||
},
|
||||
options
|
||||
);
|
||||
];
|
||||
|
||||
return {
|
||||
...state,
|
||||
routes,
|
||||
index: routes.length - 1,
|
||||
};
|
||||
}
|
||||
|
||||
const route = state.routes[index];
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
DrawerActions,
|
||||
DrawerNavigationState,
|
||||
ParamListBase,
|
||||
RouterConfigOptions,
|
||||
} from '..';
|
||||
|
||||
jest.mock('nanoid/non-secure', () => ({ nanoid: () => 'test' }));
|
||||
@@ -18,6 +19,7 @@ it('gets initial state from route names and params with initialRouteName', () =>
|
||||
baz: { answer: 42 },
|
||||
qux: { name: 'Jane' },
|
||||
},
|
||||
routeGetIdList: {},
|
||||
})
|
||||
).toEqual({
|
||||
index: 1,
|
||||
@@ -47,6 +49,7 @@ it('gets initial state from route names and params without initialRouteName', ()
|
||||
baz: { answer: 42 },
|
||||
qux: { name: 'Jane' },
|
||||
},
|
||||
routeGetIdList: {},
|
||||
})
|
||||
).toEqual({
|
||||
index: 0,
|
||||
@@ -66,12 +69,13 @@ it('gets initial state from route names and params without initialRouteName', ()
|
||||
it('gets rehydrated state from partial state', () => {
|
||||
const router = DrawerRouter({});
|
||||
|
||||
const options = {
|
||||
const options: RouterConfigOptions = {
|
||||
routeNames: ['bar', 'baz', 'qux'],
|
||||
routeParamList: {
|
||||
baz: { answer: 42 },
|
||||
qux: { name: 'Jane' },
|
||||
},
|
||||
routeGetIdList: {},
|
||||
};
|
||||
|
||||
expect(
|
||||
@@ -223,15 +227,17 @@ it("doesn't rehydrate state if it's not stale", () => {
|
||||
router.getRehydratedState(state, {
|
||||
routeNames: [],
|
||||
routeParamList: {},
|
||||
routeGetIdList: {},
|
||||
})
|
||||
).toBe(state);
|
||||
});
|
||||
|
||||
it('handles navigate action', () => {
|
||||
const router = DrawerRouter({});
|
||||
const options = {
|
||||
const options: RouterConfigOptions = {
|
||||
routeNames: ['baz', 'bar'],
|
||||
routeParamList: {},
|
||||
routeGetIdList: {},
|
||||
};
|
||||
|
||||
expect(
|
||||
@@ -267,9 +273,10 @@ it('handles navigate action', () => {
|
||||
|
||||
it('handles navigate action with open drawer', () => {
|
||||
const router = DrawerRouter({});
|
||||
const options = {
|
||||
const options: RouterConfigOptions = {
|
||||
routeNames: ['baz', 'bar'],
|
||||
routeParamList: {},
|
||||
routeGetIdList: {},
|
||||
};
|
||||
|
||||
expect(
|
||||
@@ -305,9 +312,10 @@ it('handles navigate action with open drawer', () => {
|
||||
|
||||
it('handles open drawer action', () => {
|
||||
const router = DrawerRouter({});
|
||||
const options = {
|
||||
const options: RouterConfigOptions = {
|
||||
routeNames: ['baz', 'bar'],
|
||||
routeParamList: {},
|
||||
routeGetIdList: {},
|
||||
};
|
||||
|
||||
expect(
|
||||
@@ -360,9 +368,10 @@ it('handles open drawer action', () => {
|
||||
|
||||
it('handles close drawer action', () => {
|
||||
const router = DrawerRouter({});
|
||||
const options = {
|
||||
const options: RouterConfigOptions = {
|
||||
routeNames: ['baz', 'bar'],
|
||||
routeParamList: {},
|
||||
routeGetIdList: {},
|
||||
};
|
||||
|
||||
expect(
|
||||
@@ -418,9 +427,10 @@ it('handles close drawer action', () => {
|
||||
|
||||
it('handles toggle drawer action', () => {
|
||||
const router = DrawerRouter({});
|
||||
const options = {
|
||||
const options: RouterConfigOptions = {
|
||||
routeNames: ['baz', 'bar'],
|
||||
routeParamList: {},
|
||||
routeGetIdList: {},
|
||||
};
|
||||
|
||||
expect(
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { CommonActions, StackRouter, StackActions } from '..';
|
||||
import {
|
||||
CommonActions,
|
||||
StackRouter,
|
||||
StackActions,
|
||||
RouterConfigOptions,
|
||||
} from '..';
|
||||
|
||||
jest.mock('nanoid/non-secure', () => ({ nanoid: () => 'test' }));
|
||||
|
||||
@@ -12,6 +17,7 @@ it('gets initial state from route names and params with initialRouteName', () =>
|
||||
baz: { answer: 42 },
|
||||
qux: { name: 'Jane' },
|
||||
},
|
||||
routeGetIdList: {},
|
||||
})
|
||||
).toEqual({
|
||||
index: 0,
|
||||
@@ -33,6 +39,7 @@ it('gets initial state from route names and params without initialRouteName', ()
|
||||
baz: { answer: 42 },
|
||||
qux: { name: 'Jane' },
|
||||
},
|
||||
routeGetIdList: {},
|
||||
})
|
||||
).toEqual({
|
||||
index: 0,
|
||||
@@ -47,12 +54,13 @@ it('gets initial state from route names and params without initialRouteName', ()
|
||||
it('gets rehydrated state from partial state', () => {
|
||||
const router = StackRouter({});
|
||||
|
||||
const options = {
|
||||
const options: RouterConfigOptions = {
|
||||
routeNames: ['bar', 'baz', 'qux'],
|
||||
routeParamList: {
|
||||
baz: { answer: 42 },
|
||||
qux: { name: 'Jane' },
|
||||
},
|
||||
routeGetIdList: {},
|
||||
};
|
||||
|
||||
expect(
|
||||
@@ -136,6 +144,7 @@ it("doesn't rehydrate state if it's not stale", () => {
|
||||
router.getRehydratedState(state, {
|
||||
routeNames: [],
|
||||
routeParamList: {},
|
||||
routeGetIdList: {},
|
||||
})
|
||||
).toBe(state);
|
||||
});
|
||||
@@ -163,6 +172,7 @@ it('gets state on route names change', () => {
|
||||
qux: { name: 'John' },
|
||||
fiz: { fruit: 'apple' },
|
||||
},
|
||||
routeGetIdList: {},
|
||||
}
|
||||
)
|
||||
).toEqual({
|
||||
@@ -195,6 +205,7 @@ it('gets state on route names change', () => {
|
||||
routeParamList: {
|
||||
baz: { name: 'John' },
|
||||
},
|
||||
routeGetIdList: {},
|
||||
}
|
||||
)
|
||||
).toEqual({
|
||||
@@ -228,6 +239,7 @@ it('gets state on route names change with initialRouteName', () => {
|
||||
routeParamList: {
|
||||
baz: { name: 'John' },
|
||||
},
|
||||
routeGetIdList: {},
|
||||
}
|
||||
)
|
||||
).toEqual({
|
||||
@@ -242,9 +254,10 @@ it('gets state on route names change with initialRouteName', () => {
|
||||
|
||||
it('handles navigate action', () => {
|
||||
const router = StackRouter({});
|
||||
const options = {
|
||||
const options: RouterConfigOptions = {
|
||||
routeNames: ['baz', 'bar', 'qux'],
|
||||
routeParamList: {},
|
||||
routeGetIdList: {},
|
||||
};
|
||||
|
||||
expect(
|
||||
@@ -427,11 +440,139 @@ it('handles navigate action', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('handles go back action', () => {
|
||||
it('ensures unique ID for navigate', () => {
|
||||
const router = StackRouter({});
|
||||
const options = {
|
||||
const options: RouterConfigOptions = {
|
||||
routeNames: ['baz', 'bar', 'qux'],
|
||||
routeParamList: {},
|
||||
routeGetIdList: {
|
||||
bar: ({ params }) => params?.foo,
|
||||
qux: ({ params }) => params?.fux,
|
||||
},
|
||||
};
|
||||
|
||||
expect(
|
||||
router.getStateForAction(
|
||||
{
|
||||
stale: false,
|
||||
type: 'stack',
|
||||
key: 'root',
|
||||
index: 0,
|
||||
routeNames: ['baz', 'bar', 'qux'],
|
||||
routes: [{ key: 'bar', name: 'bar' }],
|
||||
},
|
||||
CommonActions.navigate('bar', { foo: 'a' }),
|
||||
options
|
||||
)
|
||||
).toEqual({
|
||||
stale: false,
|
||||
type: 'stack',
|
||||
key: 'root',
|
||||
index: 1,
|
||||
routeNames: ['baz', 'bar', 'qux'],
|
||||
routes: [
|
||||
{ key: 'bar', name: 'bar' },
|
||||
{ key: 'bar-test', name: 'bar', params: { foo: 'a' } },
|
||||
],
|
||||
});
|
||||
|
||||
expect(
|
||||
router.getStateForAction(
|
||||
{
|
||||
stale: false,
|
||||
type: 'stack',
|
||||
key: 'root',
|
||||
index: 1,
|
||||
routeNames: ['baz', 'bar', 'qux'],
|
||||
routes: [
|
||||
{ key: 'bar', name: 'bar' },
|
||||
{ key: 'bar-test', name: 'bar', params: { foo: 'a' } },
|
||||
],
|
||||
},
|
||||
CommonActions.navigate('bar', { foo: 'a' }),
|
||||
options
|
||||
)
|
||||
).toEqual({
|
||||
stale: false,
|
||||
type: 'stack',
|
||||
key: 'root',
|
||||
index: 1,
|
||||
routeNames: ['baz', 'bar', 'qux'],
|
||||
routes: [
|
||||
{ key: 'bar', name: 'bar' },
|
||||
{ key: 'bar-test', name: 'bar', params: { foo: 'a' } },
|
||||
],
|
||||
});
|
||||
|
||||
expect(
|
||||
router.getStateForAction(
|
||||
{
|
||||
stale: false,
|
||||
type: 'stack',
|
||||
key: 'root',
|
||||
index: 1,
|
||||
routeNames: ['baz', 'bar', 'qux'],
|
||||
routes: [
|
||||
{ key: 'bar', name: 'bar' },
|
||||
{ key: 'bar-test', name: 'bar', params: { foo: 'a' } },
|
||||
],
|
||||
},
|
||||
CommonActions.navigate('bar', { foo: 'b' }),
|
||||
options
|
||||
)
|
||||
).toEqual({
|
||||
stale: false,
|
||||
type: 'stack',
|
||||
key: 'root',
|
||||
index: 2,
|
||||
routeNames: ['baz', 'bar', 'qux'],
|
||||
routes: [
|
||||
{ key: 'bar', name: 'bar' },
|
||||
{ key: 'bar-test', name: 'bar', params: { foo: 'a' } },
|
||||
{ key: 'bar-test', name: 'bar', params: { foo: 'b' } },
|
||||
],
|
||||
});
|
||||
|
||||
expect(
|
||||
router.getStateForAction(
|
||||
{
|
||||
stale: false,
|
||||
type: 'stack',
|
||||
key: 'root',
|
||||
index: 1,
|
||||
routeNames: ['baz', 'bar', 'qux'],
|
||||
routes: [
|
||||
{ key: 'bar', name: 'bar' },
|
||||
{ key: 'bar-test', name: 'bar', params: { foo: 'a' } },
|
||||
],
|
||||
},
|
||||
CommonActions.navigate({
|
||||
key: 'test',
|
||||
name: 'bar',
|
||||
params: { foo: 'a' },
|
||||
}),
|
||||
options
|
||||
)
|
||||
).toEqual({
|
||||
stale: false,
|
||||
type: 'stack',
|
||||
key: 'root',
|
||||
index: 2,
|
||||
routeNames: ['baz', 'bar', 'qux'],
|
||||
routes: [
|
||||
{ key: 'bar', name: 'bar' },
|
||||
{ key: 'bar-test', name: 'bar', params: { foo: 'a' } },
|
||||
{ key: 'test', name: 'bar', params: { foo: 'a' } },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('handles go back action', () => {
|
||||
const router = StackRouter({});
|
||||
const options: RouterConfigOptions = {
|
||||
routeNames: ['baz', 'bar', 'qux'],
|
||||
routeParamList: {},
|
||||
routeGetIdList: {},
|
||||
};
|
||||
|
||||
expect(
|
||||
@@ -477,9 +618,10 @@ it('handles go back action', () => {
|
||||
|
||||
it('handles pop action', () => {
|
||||
const router = StackRouter({});
|
||||
const options = {
|
||||
const options: RouterConfigOptions = {
|
||||
routeNames: ['baz', 'bar', 'qux'],
|
||||
routeParamList: {},
|
||||
routeGetIdList: {},
|
||||
};
|
||||
|
||||
expect(
|
||||
@@ -650,9 +792,10 @@ it('handles pop action', () => {
|
||||
|
||||
it('handles pop to top action', () => {
|
||||
const router = StackRouter({});
|
||||
const options = {
|
||||
const options: RouterConfigOptions = {
|
||||
routeNames: ['baz', 'bar', 'qux'],
|
||||
routeParamList: {},
|
||||
routeGetIdList: {},
|
||||
};
|
||||
|
||||
expect(
|
||||
@@ -684,9 +827,10 @@ it('handles pop to top action', () => {
|
||||
|
||||
it('replaces focused screen with replace', () => {
|
||||
const router = StackRouter({});
|
||||
const options = {
|
||||
const options: RouterConfigOptions = {
|
||||
routeNames: ['foo', 'bar', 'baz', 'qux'],
|
||||
routeParamList: {},
|
||||
routeGetIdList: {},
|
||||
};
|
||||
|
||||
expect(
|
||||
@@ -722,9 +866,10 @@ it('replaces focused screen with replace', () => {
|
||||
|
||||
it('replaces active screen with replace', () => {
|
||||
const router = StackRouter({});
|
||||
const options = {
|
||||
const options: RouterConfigOptions = {
|
||||
routeNames: ['foo', 'bar', 'baz', 'qux'],
|
||||
routeParamList: {},
|
||||
routeGetIdList: {},
|
||||
};
|
||||
|
||||
expect(
|
||||
@@ -763,9 +908,10 @@ it('replaces active screen with replace', () => {
|
||||
|
||||
it("doesn't handle replace if source key isn't present", () => {
|
||||
const router = StackRouter({});
|
||||
const options = {
|
||||
const options: RouterConfigOptions = {
|
||||
routeNames: ['foo', 'bar', 'baz', 'qux'],
|
||||
routeParamList: {},
|
||||
routeGetIdList: {},
|
||||
};
|
||||
|
||||
expect(
|
||||
@@ -794,9 +940,10 @@ it("doesn't handle replace if source key isn't present", () => {
|
||||
|
||||
it("doesn't handle replace if screen to replace with isn't present", () => {
|
||||
const router = StackRouter({});
|
||||
const options = {
|
||||
const options: RouterConfigOptions = {
|
||||
routeNames: ['foo', 'bar', 'baz', 'qux'],
|
||||
routeParamList: {},
|
||||
routeGetIdList: {},
|
||||
};
|
||||
|
||||
expect(
|
||||
@@ -824,11 +971,12 @@ it("doesn't handle replace if screen to replace with isn't present", () => {
|
||||
|
||||
it('handles push action', () => {
|
||||
const router = StackRouter({});
|
||||
const options = {
|
||||
const options: RouterConfigOptions = {
|
||||
routeNames: ['baz', 'bar', 'qux'],
|
||||
routeParamList: {
|
||||
baz: { foo: 21 },
|
||||
},
|
||||
routeGetIdList: {},
|
||||
};
|
||||
|
||||
expect(
|
||||
@@ -895,6 +1043,18 @@ it('handles push action', () => {
|
||||
options
|
||||
)
|
||||
).toBe(null);
|
||||
});
|
||||
|
||||
it('ensures unique ID for push', () => {
|
||||
const router = StackRouter({});
|
||||
const options: RouterConfigOptions = {
|
||||
routeNames: ['baz', 'bar', 'qux'],
|
||||
routeParamList: {},
|
||||
routeGetIdList: {
|
||||
bar: ({ params }) => params?.foo,
|
||||
qux: ({ params }) => params?.fux,
|
||||
},
|
||||
};
|
||||
|
||||
expect(
|
||||
router.getStateForAction(
|
||||
@@ -902,18 +1062,67 @@ it('handles push action', () => {
|
||||
stale: false,
|
||||
type: 'stack',
|
||||
key: 'root',
|
||||
index: 2,
|
||||
index: 0,
|
||||
routeNames: ['baz', 'bar', 'qux'],
|
||||
routes: [{ key: 'bar', name: 'bar' }],
|
||||
},
|
||||
StackActions.push('bar', { foo: 'a' }),
|
||||
options
|
||||
)
|
||||
).toEqual({
|
||||
stale: false,
|
||||
type: 'stack',
|
||||
key: 'root',
|
||||
index: 1,
|
||||
routeNames: ['baz', 'bar', 'qux'],
|
||||
routes: [
|
||||
{ key: 'bar', name: 'bar' },
|
||||
{ key: 'bar-test', name: 'bar', params: { foo: 'a' } },
|
||||
],
|
||||
});
|
||||
|
||||
expect(
|
||||
router.getStateForAction(
|
||||
{
|
||||
stale: false,
|
||||
type: 'stack',
|
||||
key: 'root',
|
||||
index: 1,
|
||||
routeNames: ['baz', 'bar', 'qux'],
|
||||
routes: [
|
||||
{ key: 'bar-3', name: 'bar' },
|
||||
{ key: 'bar-4', name: 'bar', params: { foo: 21 } },
|
||||
{ key: 'baz-5', name: 'baz' },
|
||||
{ key: 'bar', name: 'bar' },
|
||||
{ key: 'bar-test', name: 'bar', params: { foo: 'a' } },
|
||||
],
|
||||
},
|
||||
StackActions.push('bar', { foo: 'a' }),
|
||||
options
|
||||
)
|
||||
).toEqual({
|
||||
stale: false,
|
||||
type: 'stack',
|
||||
key: 'root',
|
||||
index: 1,
|
||||
routeNames: ['baz', 'bar', 'qux'],
|
||||
routes: [
|
||||
{ key: 'bar', name: 'bar' },
|
||||
{ key: 'bar-test', name: 'bar', params: { foo: 'a' } },
|
||||
],
|
||||
});
|
||||
|
||||
expect(
|
||||
router.getStateForAction(
|
||||
{
|
||||
type: 'PUSH',
|
||||
payload: { name: 'bar', key: 'bar-4', params: { bar: 29 } },
|
||||
stale: false,
|
||||
type: 'stack',
|
||||
key: 'root',
|
||||
index: 1,
|
||||
routeNames: ['baz', 'bar', 'qux'],
|
||||
routes: [
|
||||
{ key: 'bar', name: 'bar' },
|
||||
{ key: 'bar-test', name: 'bar', params: { foo: 'a' } },
|
||||
],
|
||||
},
|
||||
StackActions.push('bar', { foo: 'b' }),
|
||||
options
|
||||
)
|
||||
).toEqual({
|
||||
@@ -923,100 +1132,21 @@ it('handles push action', () => {
|
||||
index: 2,
|
||||
routeNames: ['baz', 'bar', 'qux'],
|
||||
routes: [
|
||||
{ key: 'bar-3', name: 'bar' },
|
||||
{ key: 'baz-5', name: 'baz' },
|
||||
{ key: 'bar-4', name: 'bar', params: { foo: 21, bar: 29 } },
|
||||
{ key: 'bar', name: 'bar' },
|
||||
{ key: 'bar-test', name: 'bar', params: { foo: 'a' } },
|
||||
{ key: 'bar-test', name: 'bar', params: { foo: 'b' } },
|
||||
],
|
||||
});
|
||||
|
||||
expect(
|
||||
router.getStateForAction(
|
||||
{
|
||||
stale: false,
|
||||
type: 'stack',
|
||||
key: 'root',
|
||||
index: 2,
|
||||
routeNames: ['baz', 'bar', 'qux'],
|
||||
routes: [
|
||||
{ key: 'bar-3', name: 'bar' },
|
||||
{ key: 'bar-4', name: 'bar' },
|
||||
{ key: 'baz-5', name: 'baz' },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'PUSH',
|
||||
payload: { name: 'bar', key: 'bar-6', params: { bar: 29 } },
|
||||
},
|
||||
options
|
||||
)
|
||||
).toEqual({
|
||||
stale: false,
|
||||
type: 'stack',
|
||||
key: 'root',
|
||||
index: 3,
|
||||
routeNames: ['baz', 'bar', 'qux'],
|
||||
routes: [
|
||||
{ key: 'bar-3', name: 'bar' },
|
||||
{ key: 'bar-4', name: 'bar' },
|
||||
{ key: 'baz-5', name: 'baz' },
|
||||
{ key: 'bar-6', name: 'bar', params: { bar: 29 } },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('changes index on focus change', () => {
|
||||
const router = StackRouter({});
|
||||
|
||||
expect(
|
||||
router.getStateForRouteFocus(
|
||||
{
|
||||
index: 2,
|
||||
key: 'stack-test',
|
||||
routeNames: ['bar', 'baz', 'qux'],
|
||||
routes: [
|
||||
{ key: 'bar-0', name: 'bar' },
|
||||
{ key: 'baz-0', name: 'baz' },
|
||||
{ key: 'qux-0', name: 'qux' },
|
||||
],
|
||||
stale: false,
|
||||
type: 'stack',
|
||||
},
|
||||
'baz-0'
|
||||
)
|
||||
).toEqual({
|
||||
index: 1,
|
||||
key: 'stack-test',
|
||||
routeNames: ['bar', 'baz', 'qux'],
|
||||
routes: [
|
||||
{ key: 'bar-0', name: 'bar' },
|
||||
{ key: 'baz-0', name: 'baz' },
|
||||
],
|
||||
stale: false,
|
||||
type: 'stack',
|
||||
});
|
||||
|
||||
const state = {
|
||||
index: 0,
|
||||
key: 'stack-test',
|
||||
routeNames: ['bar', 'baz', 'qux'],
|
||||
routes: [
|
||||
{ key: 'bar-0', name: 'bar' },
|
||||
{ key: 'baz-0', name: 'baz' },
|
||||
],
|
||||
stale: false as const,
|
||||
type: 'stack' as const,
|
||||
};
|
||||
|
||||
expect(router.getStateForRouteFocus(state, 'qux-0')).toEqual(state);
|
||||
});
|
||||
|
||||
it("doesn't merge params on navigate to an existing screen", () => {
|
||||
const router = StackRouter({});
|
||||
const options = {
|
||||
const options: RouterConfigOptions = {
|
||||
routeNames: ['baz', 'bar', 'qux'],
|
||||
routeParamList: {
|
||||
bar: { color: 'test' },
|
||||
},
|
||||
routeGetIdList: {},
|
||||
};
|
||||
|
||||
expect(
|
||||
@@ -1079,12 +1209,13 @@ it("doesn't merge params on navigate to an existing screen", () => {
|
||||
|
||||
it('merges params on navigate to an existing screen if merge: true', () => {
|
||||
const router = StackRouter({});
|
||||
const options = {
|
||||
const options: RouterConfigOptions = {
|
||||
routeNames: ['baz', 'bar', 'qux'],
|
||||
routeParamList: {
|
||||
bar: { color: 'test' },
|
||||
baz: { foo: 12 },
|
||||
},
|
||||
routeGetIdList: {},
|
||||
};
|
||||
|
||||
expect(
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
TabActions,
|
||||
TabNavigationState,
|
||||
ParamListBase,
|
||||
RouterConfigOptions,
|
||||
} from '..';
|
||||
|
||||
jest.mock('nanoid/non-secure', () => ({ nanoid: () => 'test' }));
|
||||
@@ -18,6 +19,7 @@ it('gets initial state from route names and params with initialRouteName', () =>
|
||||
baz: { answer: 42 },
|
||||
qux: { name: 'Jane' },
|
||||
},
|
||||
routeGetIdList: {},
|
||||
})
|
||||
).toEqual({
|
||||
index: 1,
|
||||
@@ -47,6 +49,7 @@ it('gets initial state from route names and params without initialRouteName', ()
|
||||
baz: { answer: 42 },
|
||||
qux: { name: 'Jane' },
|
||||
},
|
||||
routeGetIdList: {},
|
||||
})
|
||||
).toEqual({
|
||||
index: 0,
|
||||
@@ -66,12 +69,13 @@ it('gets initial state from route names and params without initialRouteName', ()
|
||||
it('gets rehydrated state from partial state', () => {
|
||||
const router = TabRouter({});
|
||||
|
||||
const options = {
|
||||
const options: RouterConfigOptions = {
|
||||
routeNames: ['bar', 'baz', 'qux'],
|
||||
routeParamList: {
|
||||
baz: { answer: 42 },
|
||||
qux: { name: 'Jane' },
|
||||
},
|
||||
routeGetIdList: {},
|
||||
};
|
||||
|
||||
expect(
|
||||
@@ -250,6 +254,7 @@ it("doesn't rehydrate state if it's not stale", () => {
|
||||
router.getRehydratedState(state, {
|
||||
routeNames: [],
|
||||
routeParamList: {},
|
||||
routeGetIdList: {},
|
||||
})
|
||||
).toBe(state);
|
||||
});
|
||||
@@ -257,9 +262,10 @@ it("doesn't rehydrate state if it's not stale", () => {
|
||||
it('restores correct history on rehydrating with backBehavior: order', () => {
|
||||
const router = TabRouter({ backBehavior: 'order' });
|
||||
|
||||
const options = {
|
||||
const options: RouterConfigOptions = {
|
||||
routeNames: ['foo', 'bar', 'baz', 'qux'],
|
||||
routeParamList: {},
|
||||
routeGetIdList: {},
|
||||
};
|
||||
|
||||
expect(
|
||||
@@ -298,9 +304,10 @@ it('restores correct history on rehydrating with backBehavior: order', () => {
|
||||
it('restores correct history on rehydrating with backBehavior: history', () => {
|
||||
const router = TabRouter({ backBehavior: 'history' });
|
||||
|
||||
const options = {
|
||||
const options: RouterConfigOptions = {
|
||||
routeNames: ['foo', 'bar', 'baz', 'qux'],
|
||||
routeParamList: {},
|
||||
routeGetIdList: {},
|
||||
};
|
||||
|
||||
expect(
|
||||
@@ -338,9 +345,10 @@ it('restores correct history on rehydrating with backBehavior: firstRoute', () =
|
||||
initialRouteName: 'bar',
|
||||
});
|
||||
|
||||
const options = {
|
||||
const options: RouterConfigOptions = {
|
||||
routeNames: ['foo', 'bar', 'baz', 'qux'],
|
||||
routeParamList: {},
|
||||
routeGetIdList: {},
|
||||
};
|
||||
|
||||
expect(
|
||||
@@ -381,9 +389,10 @@ it('restores correct history on rehydrating with backBehavior: initialRoute', ()
|
||||
initialRouteName: 'bar',
|
||||
});
|
||||
|
||||
const options = {
|
||||
const options: RouterConfigOptions = {
|
||||
routeNames: ['foo', 'bar', 'baz', 'qux'],
|
||||
routeParamList: {},
|
||||
routeGetIdList: {},
|
||||
};
|
||||
|
||||
expect(
|
||||
@@ -421,9 +430,10 @@ it('restores correct history on rehydrating with backBehavior: initialRoute', ()
|
||||
it('restores correct history on rehydrating with backBehavior: none', () => {
|
||||
const router = TabRouter({ backBehavior: 'none' });
|
||||
|
||||
const options = {
|
||||
const options: RouterConfigOptions = {
|
||||
routeNames: ['foo', 'bar', 'baz', 'qux'],
|
||||
routeParamList: {},
|
||||
routeGetIdList: {},
|
||||
};
|
||||
|
||||
expect(
|
||||
@@ -479,6 +489,7 @@ it('gets state on route names change', () => {
|
||||
qux: { name: 'John' },
|
||||
fiz: { fruit: 'apple' },
|
||||
},
|
||||
routeGetIdList: {},
|
||||
}
|
||||
)
|
||||
).toEqual({
|
||||
@@ -513,6 +524,7 @@ it('gets state on route names change', () => {
|
||||
{
|
||||
routeNames: ['foo', 'fiz'],
|
||||
routeParamList: {},
|
||||
routeGetIdList: {},
|
||||
}
|
||||
)
|
||||
).toEqual({
|
||||
@@ -553,6 +565,7 @@ it('preserves focused route on route names change', () => {
|
||||
qux: { name: 'John' },
|
||||
fiz: { fruit: 'apple' },
|
||||
},
|
||||
routeGetIdList: {},
|
||||
}
|
||||
)
|
||||
).toEqual({
|
||||
@@ -595,6 +608,7 @@ it('falls back to first route if route is removed on route names change', () =>
|
||||
qux: { name: 'John' },
|
||||
fiz: { fruit: 'apple' },
|
||||
},
|
||||
routeGetIdList: {},
|
||||
}
|
||||
)
|
||||
).toEqual({
|
||||
@@ -614,9 +628,10 @@ it('falls back to first route if route is removed on route names change', () =>
|
||||
|
||||
it('handles navigate action', () => {
|
||||
const router = TabRouter({});
|
||||
const options = {
|
||||
const options: RouterConfigOptions = {
|
||||
routeNames: ['bar', 'baz'],
|
||||
routeParamList: {},
|
||||
routeGetIdList: {},
|
||||
};
|
||||
|
||||
expect(
|
||||
@@ -704,9 +719,10 @@ it('handles navigate action', () => {
|
||||
|
||||
it('handles jump to action', () => {
|
||||
const router = TabRouter({});
|
||||
const options = {
|
||||
const options: RouterConfigOptions = {
|
||||
routeNames: ['bar', 'baz'],
|
||||
routeParamList: {},
|
||||
routeGetIdList: {},
|
||||
};
|
||||
|
||||
expect(
|
||||
@@ -745,9 +761,10 @@ it('handles jump to action', () => {
|
||||
|
||||
it('handles back action with backBehavior: history', () => {
|
||||
const router = TabRouter({ backBehavior: 'history' });
|
||||
const options = {
|
||||
const options: RouterConfigOptions = {
|
||||
routeNames: ['bar', 'baz', 'qux'],
|
||||
routeParamList: {},
|
||||
routeGetIdList: {},
|
||||
};
|
||||
|
||||
let state = router.getInitialState(options);
|
||||
@@ -831,9 +848,10 @@ it('handles back action with backBehavior: history', () => {
|
||||
|
||||
it('handles back action with backBehavior: order', () => {
|
||||
const router = TabRouter({ backBehavior: 'order' });
|
||||
const options = {
|
||||
const options: RouterConfigOptions = {
|
||||
routeNames: ['bar', 'baz', 'qux'],
|
||||
routeParamList: {},
|
||||
routeGetIdList: {},
|
||||
};
|
||||
|
||||
let state = router.getInitialState(options);
|
||||
@@ -902,9 +920,10 @@ it('handles back action with backBehavior: order', () => {
|
||||
|
||||
it('handles back action with backBehavior: initialRoute', () => {
|
||||
const router = TabRouter({ backBehavior: 'initialRoute' });
|
||||
const options = {
|
||||
const options: RouterConfigOptions = {
|
||||
routeNames: ['bar', 'baz', 'qux'],
|
||||
routeParamList: {},
|
||||
routeGetIdList: {},
|
||||
};
|
||||
|
||||
let state = router.getInitialState(options);
|
||||
@@ -974,9 +993,10 @@ it('handles back action with backBehavior: initialRoute and initialRouteName', (
|
||||
initialRouteName: 'baz',
|
||||
});
|
||||
|
||||
const options = {
|
||||
const options: RouterConfigOptions = {
|
||||
routeNames: ['bar', 'baz', 'qux'],
|
||||
routeParamList: {},
|
||||
routeGetIdList: {},
|
||||
};
|
||||
|
||||
let state = router.getInitialState(options);
|
||||
@@ -1042,9 +1062,10 @@ it('handles back action with backBehavior: initialRoute and initialRouteName', (
|
||||
|
||||
it('handles back action with backBehavior: none', () => {
|
||||
const router = TabRouter({ backBehavior: 'none' });
|
||||
const options = {
|
||||
const options: RouterConfigOptions = {
|
||||
routeNames: ['bar', 'baz', 'qux'],
|
||||
routeParamList: {},
|
||||
routeGetIdList: {},
|
||||
};
|
||||
|
||||
let state = router.getInitialState(options);
|
||||
@@ -1062,9 +1083,10 @@ it('handles back action with backBehavior: none', () => {
|
||||
|
||||
it('updates route key history on navigate and jump to', () => {
|
||||
const router = TabRouter({ backBehavior: 'history' });
|
||||
const options = {
|
||||
const options: RouterConfigOptions = {
|
||||
routeNames: ['bar', 'baz', 'qux'],
|
||||
routeParamList: {},
|
||||
routeGetIdList: {},
|
||||
};
|
||||
|
||||
let state: TabNavigationState<ParamListBase> = {
|
||||
@@ -1165,11 +1187,12 @@ it('updates route key history on focus change', () => {
|
||||
|
||||
it("doesn't merge params on navigate to an existing screen", () => {
|
||||
const router = TabRouter({});
|
||||
const options = {
|
||||
const options: RouterConfigOptions = {
|
||||
routeNames: ['baz', 'bar', 'qux'],
|
||||
routeParamList: {
|
||||
qux: { color: 'indigo' },
|
||||
},
|
||||
routeGetIdList: {},
|
||||
};
|
||||
|
||||
expect(
|
||||
@@ -1280,9 +1303,10 @@ it("doesn't merge params on navigate to an existing screen", () => {
|
||||
|
||||
it('merges params on navigate to an existing screen if merge: true', () => {
|
||||
const router = TabRouter({});
|
||||
const options = {
|
||||
const options: RouterConfigOptions = {
|
||||
routeNames: ['baz', 'bar', 'qux'],
|
||||
routeParamList: {},
|
||||
routeGetIdList: {},
|
||||
};
|
||||
|
||||
expect(
|
||||
@@ -1365,9 +1389,10 @@ it('merges params on navigate to an existing screen if merge: true', () => {
|
||||
|
||||
it("doesn't merge params on jump to an existing screen", () => {
|
||||
const router = TabRouter({});
|
||||
const options = {
|
||||
const options: RouterConfigOptions = {
|
||||
routeNames: ['baz', 'bar', 'qux'],
|
||||
routeParamList: {},
|
||||
routeGetIdList: {},
|
||||
};
|
||||
|
||||
expect(
|
||||
|
||||
@@ -132,6 +132,11 @@ export type RouterFactory<
|
||||
export type RouterConfigOptions = {
|
||||
routeNames: string[];
|
||||
routeParamList: ParamListBase;
|
||||
routeGetIdList: Record<
|
||||
string,
|
||||
| ((options: { params?: Record<string, any> }) => string | undefined)
|
||||
| undefined
|
||||
>;
|
||||
};
|
||||
|
||||
export type Router<
|
||||
|
||||
Reference in New Issue
Block a user