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:
Satyajit Sahoo
2021-01-14 03:33:44 +01:00
parent 365a2ad28c
commit b19f76bfff
9 changed files with 420 additions and 114 deletions

View File

@@ -388,6 +388,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.
*/

View File

@@ -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,
});
}
@@ -372,6 +385,7 @@ export default function useNavigationBuilder<
? router.getStateForAction(nextState, action, {
routeNames,
routeParamList,
routeGetIdList,
})
: null;
@@ -380,6 +394,7 @@ export default function useNavigationBuilder<
? router.getRehydratedState(updatedState, {
routeNames,
routeParamList,
routeGetIdList,
})
: nextState;
}
@@ -501,6 +516,7 @@ export default function useNavigationBuilder<
routerConfigOptions: {
routeNames,
routeParamList,
routeGetIdList,
},
emitter,
});

View File

@@ -78,6 +78,7 @@ export default function useNavigationHelpers<
router.getStateForAction(state, CommonActions.goBack() as Action, {
routeNames: state.routeNames,
routeParamList: {},
routeGetIdList: {},
}) !== null ||
parentNavigationHelpers?.canGoBack() ||
false

View File

@@ -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 (partialState.history ? isDrawerOpen(partialState) : openByDefault) {

View File

@@ -258,6 +258,9 @@ export default function StackRouter(options: StackRouterOptions) {
case 'PUSH':
if (state.routeNames.includes(action.payload.name)) {
const getId = options.routeGetIdList[action.payload.name];
const id = getId?.({ params: action.payload.params });
const route =
action.payload.name && action.payload.key
? state.routes.find(
@@ -265,34 +268,32 @@ export default function StackRouter(options: StackRouterOptions) {
route.name === action.payload.name &&
route.key === action.payload.key
)
: 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:
action.payload.params !== undefined
? {
...route.params,
...action.payload.params,
}
: route.params,
});
} else {
routes = [
...state.routes,
{
key:
action.payload.key === undefined
? `${action.payload.name}-${nanoid()}`
: action.payload.key,
action.payload.key ?? `${action.payload.name}-${nanoid()}`,
name: action.payload.name,
params:
routeParamList[action.payload.name] !== undefined
@@ -355,7 +356,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 +396,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];

View File

@@ -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,
@@ -44,6 +46,7 @@ it('gets initial state from route names and params without initialRouteName', ()
baz: { answer: 42 },
qux: { name: 'Jane' },
},
routeGetIdList: {},
})
).toEqual({
index: 0,
@@ -70,6 +73,7 @@ it('gets initial state from route names and params with openByDefault', () => {
baz: { answer: 42 },
qux: { name: 'Jane' },
},
routeGetIdList: {},
})
).toEqual({
index: 0,
@@ -89,12 +93,13 @@ it('gets initial state from route names and params with openByDefault', () => {
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(
@@ -244,6 +249,7 @@ it("doesn't rehydrate state if it's not stale", () => {
router.getRehydratedState(state, {
routeNames: [],
routeParamList: {},
routeGetIdList: {},
})
).toBe(state);
});
@@ -251,12 +257,13 @@ it("doesn't rehydrate state if it's not stale", () => {
it('respects openByDefault when rehydrating', () => {
const router = DrawerRouter({ openByDefault: true });
const options = {
const options: RouterConfigOptions = {
routeNames: ['bar', 'baz', 'qux'],
routeParamList: {
baz: { answer: 42 },
qux: { name: 'Jane' },
},
routeGetIdList: {},
};
expect(
@@ -319,9 +326,10 @@ it('respects openByDefault when rehydrating', () => {
it('handles navigate action', () => {
const router = DrawerRouter({});
const options = {
const options: RouterConfigOptions = {
routeNames: ['baz', 'bar'],
routeParamList: {},
routeGetIdList: {},
};
expect(
@@ -360,9 +368,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(
@@ -401,9 +410,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(
@@ -456,9 +466,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(
@@ -514,9 +525,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(

View File

@@ -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,100 @@ 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(
{
stale: false,
type: 'stack',
key: 'root',
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', 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(
{
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({
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(
@@ -964,57 +1206,12 @@ it('handles push action', () => {
});
});
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('merges params on navigate to an existing screen', () => {
const router = StackRouter({});
const options = {
const options: RouterConfigOptions = {
routeNames: ['baz', 'bar', 'qux'],
routeParamList: {},
routeGetIdList: {},
};
expect(
@@ -1077,11 +1274,12 @@ it('merges params on navigate to an existing screen', () => {
it("doesn't merge params on navigate to an existing screen if merge: false", () => {
const router = StackRouter({});
const options = {
const options: RouterConfigOptions = {
routeNames: ['baz', 'bar', 'qux'],
routeParamList: {
baz: { foo: 12 },
},
routeGetIdList: {},
};
expect(

View File

@@ -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,
@@ -44,6 +46,7 @@ it('gets initial state from route names and params without initialRouteName', ()
baz: { answer: 42 },
qux: { name: 'Jane' },
},
routeGetIdList: {},
})
).toEqual({
index: 0,
@@ -63,12 +66,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(
@@ -241,6 +245,7 @@ it("doesn't rehydrate state if it's not stale", () => {
router.getRehydratedState(state, {
routeNames: [],
routeParamList: {},
routeGetIdList: {},
})
).toBe(state);
});
@@ -248,9 +253,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(
@@ -289,9 +295,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(
@@ -329,9 +336,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(
@@ -372,9 +380,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(
@@ -412,9 +421,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(
@@ -470,6 +480,7 @@ it('gets state on route names change', () => {
qux: { name: 'John' },
fiz: { fruit: 'apple' },
},
routeGetIdList: {},
}
)
).toEqual({
@@ -504,6 +515,7 @@ it('gets state on route names change', () => {
{
routeNames: ['foo', 'fiz'],
routeParamList: {},
routeGetIdList: {},
}
)
).toEqual({
@@ -544,6 +556,7 @@ it('preserves focused route on route names change', () => {
qux: { name: 'John' },
fiz: { fruit: 'apple' },
},
routeGetIdList: {},
}
)
).toEqual({
@@ -586,6 +599,7 @@ it('falls back to first route if route is removed on route names change', () =>
qux: { name: 'John' },
fiz: { fruit: 'apple' },
},
routeGetIdList: {},
}
)
).toEqual({
@@ -605,9 +619,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(
@@ -695,9 +710,10 @@ it('handles navigate action', () => {
it('handles jump to action', () => {
const router = TabRouter({});
const options = {
const options: RouterConfigOptions = {
routeNames: ['bar', 'baz'],
routeParamList: {},
routeGetIdList: {},
};
expect(
@@ -736,9 +752,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);
@@ -822,9 +839,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);
@@ -893,9 +911,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);
@@ -965,9 +984,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);
@@ -1033,9 +1053,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);
@@ -1053,9 +1074,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> = {
@@ -1156,9 +1178,10 @@ it('updates route key history on focus change', () => {
it('merges params on navigate to an existing screen', () => {
const router = TabRouter({});
const options = {
const options: RouterConfigOptions = {
routeNames: ['baz', 'bar', 'qux'],
routeParamList: {},
routeGetIdList: {},
};
expect(
@@ -1239,6 +1262,7 @@ it("doesn't merge params on navigate to an existing screen if merge: false", ()
routeParamList: {
qux: { color: 'indigo' },
},
routeGetIdList: {},
};
expect(
@@ -1281,6 +1305,17 @@ it("doesn't merge params on navigate to an existing screen if merge: false", ()
{ type: 'route', key: 'bar' },
],
});
});
it('merges params on navigate to an existing screen if merge: true', () => {
const router = TabRouter({});
const options: RouterConfigOptions = {
routeNames: ['baz', 'bar', 'qux'],
routeParamList: {
qux: { color: 'indigo' },
},
routeGetIdList: {},
};
expect(
router.getStateForAction(
@@ -1369,9 +1404,10 @@ it("doesn't merge params on navigate to an existing screen if merge: false", ()
it('merges params on jump to an existing screen', () => {
const router = TabRouter({});
const options = {
const options: RouterConfigOptions = {
routeNames: ['baz', 'bar', 'qux'],
routeParamList: {},
routeGetIdList: {},
};
expect(

View File

@@ -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<