feat: add ability to specify root param list

This commit is contained in:
Satyajit Sahoo
2021-05-09 03:44:35 +02:00
parent 1a6aebefcb
commit b28bfddc17
15 changed files with 446 additions and 291 deletions

View File

@@ -29,6 +29,7 @@ import {
DarkTheme,
PathConfigMap,
useNavigationContainerRef,
NavigatorScreenParams,
} from '@react-navigation/native';
import { createDrawerNavigator } from '@react-navigation/drawer';
import {
@@ -62,6 +63,13 @@ if (Platform.OS !== 'web') {
enableScreens();
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace ReactNavigation {
interface RootParamList extends RootStackParamList {}
}
}
type RootDrawerParamList = {
Examples: undefined;
};
@@ -120,7 +128,7 @@ const SCREENS = {
};
type RootStackParamList = {
Home: undefined;
Home: NavigatorScreenParams<RootDrawerParamList>;
NotFound: undefined;
} & {
[P in keyof typeof SCREENS]: undefined;
@@ -233,7 +241,9 @@ export default function App() {
prefixes: [createURL('/')],
config: {
initialRouteName: 'Home',
screens: Object.keys(SCREENS).reduce<PathConfigMap>(
screens: Object.keys(SCREENS).reduce<
PathConfigMap<RootStackParamList>
>(
(acc, name) => {
// Convert screen names such as SimpleStack to kebab case (simple-stack)
const path = name
@@ -241,13 +251,15 @@ export default function App() {
.replace(/^-/, '')
.toLowerCase();
// @ts-expect-error: these types aren't accurate
// But we aren't too concerned for now
acc[name] = {
path,
screens: {
Article: {
path: 'article/:author?',
parse: {
author: (author) =>
author: (author: string) =>
author.charAt(0).toUpperCase() +
author.slice(1).replace(/-/g, ' '),
},

View File

@@ -33,8 +33,10 @@ it('converts state to path string', () => {
const path = '/foo/bar/baz%20qux?author=jane&valid=true';
expect(getPathFromState(state)).toBe(path);
expect(getPathFromState(getStateFromPath(path) as State)).toBe(path);
expect(getPathFromState<object>(state)).toBe(path);
expect(
getPathFromState<object>(getStateFromPath<object>(path) as State)
).toBe(path);
});
it('converts state to path string with config', () => {
@@ -97,9 +99,12 @@ it('converts state to path string with config', () => {
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -116,8 +121,10 @@ it('handles route without param', () => {
],
};
expect(getPathFromState(state)).toBe(path);
expect(getPathFromState(getStateFromPath(path) as State)).toBe(path);
expect(getPathFromState<object>(state)).toBe(path);
expect(
getPathFromState<object>(getStateFromPath<object>(path) as State)
).toBe(path);
});
it("doesn't add query param for empty params", () => {
@@ -131,8 +138,10 @@ it("doesn't add query param for empty params", () => {
],
};
expect(getPathFromState(state)).toBe(path);
expect(getPathFromState(getStateFromPath(path) as State)).toBe(path);
expect(getPathFromState<object>(state)).toBe(path);
expect(
getPathFromState<object>(getStateFromPath<object>(path) as State)
).toBe(path);
});
it('handles state with config with nested screens', () => {
@@ -208,9 +217,12 @@ it('handles state with config with nested screens', () => {
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -287,9 +299,12 @@ it('handles state with config with nested screens and exact', () => {
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -352,9 +367,12 @@ it('handles state with config with nested screens and unused configs', () => {
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -418,9 +436,12 @@ it('handles state with config with nested screens and unused configs with exact'
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -498,9 +519,12 @@ it('handles nested object with stringify in it', () => {
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -580,9 +604,12 @@ it('handles nested object with stringify in it with exact', () => {
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -623,9 +650,12 @@ it('handles nested object for second route depth', () => {
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -669,9 +699,12 @@ it('handles nested object for second route depth with exact', () => {
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -719,9 +752,12 @@ it('handles nested object for second route depth and path and stringify in roots
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -774,9 +810,12 @@ it('handles nested object for second route depth and path and stringify in roots
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -805,9 +844,12 @@ it('ignores empty string paths', () => {
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -850,9 +892,12 @@ it('keeps query params if path is empty', () => {
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toEqual(path);
});
@@ -894,9 +939,12 @@ it('cuts nested configs too', () => {
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -939,9 +987,12 @@ it('cuts nested configs too with exact', () => {
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -977,9 +1028,12 @@ it('handles empty path at the end', () => {
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -1012,9 +1066,12 @@ it('returns "/" for empty path', () => {
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -1042,9 +1099,12 @@ it('parses no path specified', () => {
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -1121,9 +1181,12 @@ it('strips undefined query params', () => {
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -1201,9 +1264,12 @@ it('strips undefined query params with exact', () => {
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -1278,9 +1344,12 @@ it('handles stripping all query params', () => {
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -1357,9 +1426,12 @@ it('handles stripping all query params with exact', () => {
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -1380,9 +1452,12 @@ it('replaces undefined query params', () => {
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -1405,9 +1480,12 @@ it('matches wildcard patterns at root', () => {
routes: [{ name: '404' }],
};
expect(getPathFromState(state, config)).toBe('/404');
expect(getPathFromState<object>(state, config)).toBe('/404');
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe('/404');
});
@@ -1447,9 +1525,12 @@ it('matches wildcard patterns at nested level', () => {
],
};
expect(getPathFromState(state, config)).toBe('/bar/42/404');
expect(getPathFromState<object>(state, config)).toBe('/bar/42/404');
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe('/bar/42/404');
});
@@ -1492,9 +1573,12 @@ it('matches wildcard patterns at nested level with exact', () => {
],
};
expect(getPathFromState(state, config)).toBe('/404');
expect(getPathFromState<object>(state, config)).toBe('/404');
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe('/404');
});
@@ -1535,9 +1619,12 @@ it('tries to match wildcard patterns at the end', () => {
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -1570,8 +1657,11 @@ it('uses nearest parent wildcard match for unmatched paths', () => {
],
};
expect(getPathFromState(state, config)).toBe('/404');
expect(getPathFromState<object>(state, config)).toBe('/404');
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe('/404');
});

View File

@@ -12,7 +12,7 @@ const changePath = <T extends InitialState>(state: T, path: string): T =>
});
it('returns undefined for invalid path', () => {
expect(getStateFromPath('//')).toBe(undefined);
expect(getStateFromPath<object>('//')).toBe(undefined);
});
it('converts path string to initial state', () => {
@@ -41,8 +41,8 @@ it('converts path string to initial state', () => {
],
};
expect(getStateFromPath(path)).toEqual(state);
expect(getStateFromPath(getPathFromState(state))).toEqual(
expect(getStateFromPath<object>(path)).toEqual(state);
expect(getStateFromPath<object>(getPathFromState<object>(state))).toEqual(
changePath(state, '/foo/bar/baz%20qux?author=jane%20%26%20co&valid=true')
);
});
@@ -106,16 +106,16 @@ it('converts path string to initial state with config', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handles leading slash when converting', () => {
const path = '/foo/bar/?count=42';
expect(getStateFromPath(path)).toEqual({
expect(getStateFromPath<object>(path)).toEqual({
routes: [
{
name: 'foo',
@@ -136,7 +136,7 @@ it('handles leading slash when converting', () => {
it('handles ending slash when converting', () => {
const path = 'foo/bar/?count=42';
expect(getStateFromPath(path)).toEqual({
expect(getStateFromPath<object>(path)).toEqual({
routes: [
{
name: 'foo',
@@ -167,8 +167,8 @@ it('handles route without param', () => {
],
};
expect(getStateFromPath(path)).toEqual(state);
expect(getStateFromPath(getPathFromState(state))).toEqual(
expect(getStateFromPath<object>(path)).toEqual(state);
expect(getStateFromPath<object>(getPathFromState<object>(state))).toEqual(
changePath(state, '/foo/bar')
);
});
@@ -245,10 +245,10 @@ it('converts path string to initial state with config with nested screens', () =
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('converts path string to initial state with config with nested screens and unused parse functions', () => {
@@ -308,10 +308,10 @@ it('converts path string to initial state with config with nested screens and un
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
changePath(state, '/foe/baz/Jane?count=10&answer=42&valid=true')
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(changePath(state, '/foe/baz/Jane?count=10&answer=42&valid=true'));
});
it('handles nested object with unused configs and with parse in it', () => {
@@ -400,10 +400,10 @@ it('handles nested object with unused configs and with parse in it', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handles parse in nested object for second route depth', () => {
@@ -450,10 +450,10 @@ it('handles parse in nested object for second route depth', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handles parse in nested object for second route depth and and path and parse in roots', () => {
@@ -501,10 +501,10 @@ it('handles parse in nested object for second route depth and and path and parse
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handles initialRouteName at top level', () => {
@@ -545,10 +545,10 @@ it('handles initialRouteName at top level', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handles initialRouteName inside a screen', () => {
@@ -591,10 +591,10 @@ it('handles initialRouteName inside a screen', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handles initialRouteName included in path', () => {
@@ -633,10 +633,10 @@ it('handles initialRouteName included in path', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handles two initialRouteNames', () => {
@@ -728,10 +728,10 @@ it('handles two initialRouteNames', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('accepts initialRouteName without config for it', () => {
@@ -823,10 +823,10 @@ it('accepts initialRouteName without config for it', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('returns undefined if path is empty and no matching screen is present', () => {
@@ -847,7 +847,7 @@ it('returns undefined if path is empty and no matching screen is present', () =>
const path = '';
expect(getStateFromPath(path, config)).toEqual(undefined);
expect(getStateFromPath<object>(path, config)).toEqual(undefined);
});
it('returns matching screen if path is empty', () => {
@@ -886,10 +886,10 @@ it('returns matching screen if path is empty', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
changePath(state, '/')
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(changePath(state, '/'));
});
it('returns matching screen with params if path is empty', () => {
@@ -931,10 +931,10 @@ it('returns matching screen with params if path is empty', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
changePath(state, '/?foo=42')
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(changePath(state, '/?foo=42'));
});
it("doesn't match nested screen if path is empty", () => {
@@ -959,7 +959,7 @@ it("doesn't match nested screen if path is empty", () => {
const path = '';
expect(getStateFromPath(path, config)).toEqual(undefined);
expect(getStateFromPath<object>(path, config)).toEqual(undefined);
});
it('chooses more exhaustive pattern', () => {
@@ -1004,10 +1004,10 @@ it('chooses more exhaustive pattern', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handles same paths beginnings', () => {
@@ -1048,10 +1048,10 @@ it('handles same paths beginnings', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handles same paths beginnings with params', () => {
@@ -1096,10 +1096,10 @@ it('handles same paths beginnings with params', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handles not taking path with too many segments', () => {
@@ -1151,10 +1151,10 @@ it('handles not taking path with too many segments', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handles differently ordered params v1', () => {
@@ -1206,10 +1206,10 @@ it('handles differently ordered params v1', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handles differently ordered params v2', () => {
@@ -1261,10 +1261,10 @@ it('handles differently ordered params v2', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handles differently ordered params v3', () => {
@@ -1316,10 +1316,10 @@ it('handles differently ordered params v3', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handles differently ordered params v4', () => {
@@ -1371,10 +1371,10 @@ it('handles differently ordered params v4', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
changePath(state, '/5/foos/res/20')
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(changePath(state, '/5/foos/res/20'));
});
it('handles simple optional params', () => {
@@ -1426,10 +1426,10 @@ it('handles simple optional params', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handle 2 optional params at the end v1', () => {
@@ -1481,10 +1481,10 @@ it('handle 2 optional params at the end v1', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handle 2 optional params at the end v2', () => {
@@ -1536,10 +1536,10 @@ it('handle 2 optional params at the end v2', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handle 2 optional params at the end v3', () => {
@@ -1592,10 +1592,10 @@ it('handle 2 optional params at the end v3', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handle optional params in the middle v1', () => {
@@ -1648,10 +1648,10 @@ it('handle optional params in the middle v1', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handle optional params in the middle v2', () => {
@@ -1704,10 +1704,10 @@ it('handle optional params in the middle v2', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handle optional params in the middle v3', () => {
@@ -1761,10 +1761,10 @@ it('handle optional params in the middle v3', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handle optional params in the middle v4', () => {
@@ -1818,10 +1818,10 @@ it('handle optional params in the middle v4', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handle optional params in the middle v5', () => {
@@ -1875,10 +1875,10 @@ it('handle optional params in the middle v5', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handle optional params in the beginning v1', () => {
@@ -1932,10 +1932,10 @@ it('handle optional params in the beginning v1', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
changePath(state, '/5/10/foos/15')
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(changePath(state, '/5/10/foos/15'));
});
it('handle optional params in the beginning v2', () => {
@@ -1989,10 +1989,10 @@ it('handle optional params in the beginning v2', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
changePath(state, '/5/10/foos/15')
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(changePath(state, '/5/10/foos/15'));
});
it('merges parent patterns if needed', () => {
@@ -2030,10 +2030,10 @@ it('merges parent patterns if needed', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
changePath(state, '/foo/42/baz/babel')
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(changePath(state, '/foo/42/baz/babel'));
});
it('ignores extra slashes in the pattern', () => {
@@ -2067,10 +2067,10 @@ it('ignores extra slashes in the pattern', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('matches wildcard patterns at root', () => {
@@ -2092,10 +2092,10 @@ it('matches wildcard patterns at root', () => {
routes: [{ name: '404', path }],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
changePath(state, '/404')
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(changePath(state, '/404'));
});
it('matches wildcard patterns at nested level', () => {
@@ -2134,10 +2134,10 @@ it('matches wildcard patterns at nested level', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
changePath(state, '/bar/42/404')
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(changePath(state, '/bar/42/404'));
});
it('matches wildcard patterns at nested level with exact', () => {
@@ -2179,10 +2179,10 @@ it('matches wildcard patterns at nested level with exact', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
changePath(state, '/404')
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(changePath(state, '/404'));
});
it('tries to match wildcard patterns at the end', () => {
@@ -2222,10 +2222,10 @@ it('tries to match wildcard patterns at the end', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('uses nearest parent wildcard match for unmatched paths', () => {
@@ -2257,17 +2257,17 @@ it('uses nearest parent wildcard match for unmatched paths', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
changePath(state, '/404')
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(changePath(state, '/404'));
});
it('throws if two screens map to the same pattern', () => {
const path = '/bar/42/baz/test';
expect(() =>
getStateFromPath(path, {
getStateFromPath<object>(path, {
screens: {
Foo: {
screens: {
@@ -2287,7 +2287,7 @@ it('throws if two screens map to the same pattern', () => {
);
expect(() =>
getStateFromPath(path, {
getStateFromPath<object>(path, {
screens: {
Foo: {
screens: {
@@ -2354,10 +2354,10 @@ it('correctly applies initialRouteName for config with similar route names', ()
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('correctly applies initialRouteName for config with similar route names v2', () => {
@@ -2414,8 +2414,8 @@ it('correctly applies initialRouteName for config with similar route names v2',
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});

View File

@@ -1,11 +1,11 @@
import { CommonActions, ParamListBase } from '@react-navigation/routers';
import { CommonActions } from '@react-navigation/routers';
import type { NavigationContainerRefWithCurrent } from './types';
export const NOT_INITIALIZED_ERROR =
"The 'navigation' object hasn't been initialized yet. This might happen if you don't have a navigator mounted, or if the navigator hasn't finished mounting. See https://reactnavigation.org/docs/navigating-without-navigation-prop#handling-initialization for more details.";
export default function createNavigationContainerRef<
ParamList extends ParamListBase
ParamList extends {} = ReactNavigation.RootParamList
>(): NavigationContainerRefWithCurrent<ParamList> {
const methods = [
...Object.keys(CommonActions),

View File

@@ -13,7 +13,10 @@ type ConfigItem = {
screens?: Record<string, ConfigItem>;
};
type Options = { initialRouteName?: string; screens: PathConfigMap };
type Options = {
initialRouteName?: string;
screens: PathConfigMap<object>;
};
type NavigateAction<State extends NavigationState> = {
type: 'NAVIGATE';
@@ -29,7 +32,9 @@ export default function getActionFromState(
options?: Options
): NavigateAction<NavigationState> | CommonActions.Action | undefined {
// Create a normalized configs object which will be easier to use
const normalizedConfig = options ? createNormalizedConfigItem(options) : {};
const normalizedConfig = options
? createNormalizedConfigItem(options as PathConfig<object> | string)
: {};
const routes =
state.index != null ? state.routes.slice(0, state.index + 1) : state.routes;
@@ -130,7 +135,7 @@ export default function getActionFromState(
};
}
const createNormalizedConfigItem = (config: PathConfig | string) =>
const createNormalizedConfigItem = (config: PathConfig<object> | string) =>
typeof config === 'object' && config != null
? {
initialRouteName: config.initialRouteName,
@@ -141,7 +146,7 @@ const createNormalizedConfigItem = (config: PathConfig | string) =>
}
: {};
const createNormalizedConfigs = (options: PathConfigMap) =>
const createNormalizedConfigs = (options: PathConfigMap<object>) =>
Object.entries(options).reduce<Record<string, ConfigItem>>((acc, [k, v]) => {
acc[k] = createNormalizedConfigItem(v);
return acc;

View File

@@ -7,7 +7,10 @@ import type {
import fromEntries from './fromEntries';
import type { PathConfig, PathConfigMap } from './types';
type Options = { initialRouteName?: string; screens: PathConfigMap };
type Options<ParamList> = {
initialRouteName?: string;
screens: PathConfigMap<ParamList>;
};
type State = NavigationState | Omit<PartialState<NavigationState>, 'stale'>;
@@ -61,9 +64,9 @@ const getActiveRoute = (state: State): { name: string; params?: object } => {
* @param options Extra options to fine-tune how to serialize the path.
* @returns Path representing the state, e.g. /foo/bar?count=42.
*/
export default function getPathFromState(
export default function getPathFromState<ParamList extends {}>(
state: State,
options?: Options
options?: Options<ParamList>
): string {
if (state == null) {
throw Error(
@@ -238,7 +241,7 @@ const joinPaths = (...paths: string[]): string =>
.join('/');
const createConfigItem = (
config: PathConfig | string,
config: PathConfig<object> | string,
parentPattern?: string
): ConfigItem => {
if (typeof config === 'string') {
@@ -276,7 +279,7 @@ const createConfigItem = (
};
const createNormalizedConfigs = (
options: PathConfigMap,
options: PathConfigMap<object>,
pattern?: string
): Record<string, ConfigItem> =>
fromEntries(

View File

@@ -8,9 +8,9 @@ import type {
import findFocusedRoute from './findFocusedRoute';
import type { PathConfigMap } from './types';
type Options = {
type Options<ParamList extends {}> = {
initialRouteName?: string;
screens: PathConfigMap;
screens: PathConfigMap<ParamList>;
};
type ParseConfig = Record<string, (value: string) => any>;
@@ -60,9 +60,9 @@ type ParsedRoute = {
* @param path Path string to parse and convert, e.g. /foo/bar?count=42.
* @param options Extra options to fine-tune how to parse the path.
*/
export default function getStateFromPath(
export default function getStateFromPath<ParamList extends {}>(
path: string,
options?: Options
options?: Options<ParamList>
): ResultState | undefined {
let initialRoutes: InitialRouteConfig[] = [];
@@ -106,7 +106,7 @@ export default function getStateFromPath(
...Object.keys(screens).map((key) =>
createNormalizedConfigs(
key,
screens as PathConfigMap,
screens as PathConfigMap<object>,
[],
initialRoutes,
[]
@@ -307,7 +307,7 @@ const matchAgainstConfigs = (remaining: string, configs: RouteConfig[]) => {
const createNormalizedConfigs = (
screen: string,
routeConfig: PathConfigMap,
routeConfig: PathConfigMap<object>,
routeNames: string[] = [],
initials: InitialRouteConfig[],
parentScreens: string[],
@@ -319,6 +319,7 @@ const createNormalizedConfigs = (
parentScreens.push(screen);
// @ts-expect-error: we can't strongly typecheck this for now
const config = routeConfig[screen];
if (typeof config === 'string') {
@@ -345,7 +346,13 @@ const createNormalizedConfigs = (
: config.path || '';
configs.push(
createConfigItem(screen, routeNames, pattern, config.path, config.parse)
createConfigItem(
screen,
routeNames,
pattern!,
config.path,
config.parse
)
);
}
@@ -361,7 +368,7 @@ const createNormalizedConfigs = (
Object.keys(config.screens).forEach((nestedConfig) => {
const result = createNormalizedConfigs(
nestedConfig,
config.screens as PathConfigMap,
config.screens as PathConfigMap<object>,
routeNames,
initials,
[...parentScreens],

View File

@@ -9,6 +9,14 @@ import type {
ParamListBase,
} from '@react-navigation/routers';
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace ReactNavigation {
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface RootParamList {}
}
}
type Keyof<T extends {}> = Extract<keyof T, string>;
export type DefaultNavigatorOptions<
@@ -265,7 +273,7 @@ export type NavigationContainerProps = {
};
export type NavigationProp<
ParamList extends ParamListBase,
ParamList extends {},
RouteName extends keyof ParamList = Keyof<ParamList>,
State extends NavigationState = NavigationState<ParamList>,
ScreenOptions extends {} = {},
@@ -510,7 +518,7 @@ export type NavigationContainerEventMap = {
};
export type NavigationContainerRef<
ParamList extends ParamListBase
ParamList extends {}
> = NavigationHelpers<ParamList> &
EventConsumer<NavigationContainerEventMap> & {
/**
@@ -538,7 +546,7 @@ export type NavigationContainerRef<
};
export type NavigationContainerRefWithCurrent<
ParamList extends ParamListBase
ParamList extends {}
> = NavigationContainerRef<ParamList> & {
current: NavigationContainerRef<ParamList> | null;
};
@@ -601,15 +609,20 @@ export type NavigatorScreenParams<
};
}[keyof ParamList];
export type PathConfig = {
export type PathConfig<ParamList extends {}> = {
path?: string;
exact?: boolean;
parse?: Record<string, (value: string) => any>;
stringify?: Record<string, (value: any) => string>;
screens?: PathConfigMap;
initialRouteName?: string;
screens?: PathConfigMap<ParamList>;
initialRouteName?: keyof ParamList;
};
export type PathConfigMap = {
[routeName: string]: string | PathConfig;
export type PathConfigMap<ParamList extends {}> = {
[RouteName in keyof ParamList]?: ParamList[RouteName] extends NavigatorScreenParams<
infer T,
any
>
? string | PathConfig<T>
: string | Omit<PathConfig<{}>, 'screens' | 'initialRouteName'>;
};

View File

@@ -1,5 +1,4 @@
import * as React from 'react';
import type { ParamListBase } from '@react-navigation/routers';
import NavigationContext from './NavigationContext';
import type { NavigationProp } from './types';
@@ -9,7 +8,7 @@ import type { NavigationProp } from './types';
* @returns Navigation prop of the parent screen.
*/
export default function useNavigation<
T extends NavigationProp<ParamListBase>
T extends NavigationProp<any> = NavigationProp<ReactNavigation.RootParamList>
>(): T {
const navigation = React.useContext(NavigationContext);

View File

@@ -1,8 +1,9 @@
import * as React from 'react';
import type { ParamListBase } from '@react-navigation/core';
import type { LinkingOptions } from './types';
const LinkingContext = React.createContext<{
options: LinkingOptions | undefined;
options: LinkingOptions<ParamListBase> | undefined;
}>({ options: undefined });
LinkingContext.displayName = 'LinkingContext';

View File

@@ -14,9 +14,9 @@ import useDocumentTitle from './useDocumentTitle';
import useBackButton from './useBackButton';
import type { Theme, LinkingOptions, DocumentTitleOptions } from './types';
type Props = NavigationContainerProps & {
type Props<ParamList extends {}> = NavigationContainerProps & {
theme?: Theme;
linking?: LinkingOptions;
linking?: LinkingOptions<ParamList>;
fallback?: React.ReactNode;
documentTitle?: DocumentTitleOptions;
onReady?: () => void;
@@ -36,7 +36,7 @@ type Props = NavigationContainerProps & {
* @param props.children Child elements to render the content.
* @param props.ref Ref object which refers to the navigation object containing helper methods.
*/
const NavigationContainer = React.forwardRef(function NavigationContainer(
function NavigationContainerInner(
{
theme = DefaultTheme,
linking,
@@ -44,7 +44,7 @@ const NavigationContainer = React.forwardRef(function NavigationContainer(
documentTitle,
onReady,
...rest
}: Props,
}: Props<ParamListBase>,
ref?: React.Ref<NavigationContainerRef<ParamListBase> | null>
) {
const isLinkingEnabled = linking ? linking.enabled !== false : false;
@@ -101,6 +101,14 @@ const NavigationContainer = React.forwardRef(function NavigationContainer(
</ThemeProvider>
</LinkingContext.Provider>
);
});
}
const NavigationContainer = React.forwardRef(NavigationContainerInner) as <
RootParamList extends {} = ReactNavigation.RootParamList
>(
props: Props<RootParamList> & {
ref?: React.Ref<NavigationContainerRef<RootParamList>>;
}
) => React.ReactElement;
export default NavigationContainer;

View File

@@ -5,6 +5,7 @@ import {
StackRouter,
TabRouter,
NavigationHelpersContext,
NavigatorScreenParams,
} from '@react-navigation/core';
import { renderToString } from 'react-dom/server';
import NavigationContainer from '../NavigationContainer';
@@ -36,24 +37,37 @@ it('renders correct state with location', () => {
);
});
const Stack = createStackNavigator();
type StackAParamList = {
Home: NavigatorScreenParams<StackBParamList>;
Chat: undefined;
};
type StackBParamList = {
Profile: undefined;
Settings: undefined;
Feed: undefined;
Updates: undefined;
};
const StackA = createStackNavigator<StackAParamList>();
const StackB = createStackNavigator<StackBParamList>();
const TestScreen = ({ route }: any): any =>
`${route.name} ${JSON.stringify(route.params)}`;
const NestedStack = () => {
return (
<Stack.Navigator initialRouteName="Feed">
<Stack.Screen name="Profile" component={TestScreen} />
<Stack.Screen name="Settings" component={TestScreen} />
<Stack.Screen name="Feed" component={TestScreen} />
<Stack.Screen name="Updates" component={TestScreen} />
</Stack.Navigator>
<StackB.Navigator initialRouteName="Feed">
<StackB.Screen name="Profile" component={TestScreen} />
<StackB.Screen name="Settings" component={TestScreen} />
<StackB.Screen name="Feed" component={TestScreen} />
<StackB.Screen name="Updates" component={TestScreen} />
</StackB.Navigator>
);
};
const element = (
<NavigationContainer
<NavigationContainer<StackAParamList>
linking={{
prefixes: [],
config: {
@@ -73,10 +87,10 @@ it('renders correct state with location', () => {
},
}}
>
<Stack.Navigator>
<Stack.Screen name="Home" component={NestedStack} />
<Stack.Screen name="Chat" component={TestScreen} />
</Stack.Navigator>
<StackA.Navigator>
<StackA.Screen name="Home" component={NestedStack} />
<StackA.Screen name="Chat" component={TestScreen} />
</StackA.Navigator>
</NavigationContainer>
);

View File

@@ -17,7 +17,7 @@ export type Theme = {
};
};
export type LinkingOptions = {
export type LinkingOptions<ParamList extends {}> = {
/**
* Whether deep link handling should be enabled.
* Defaults to true.
@@ -53,7 +53,10 @@ export type LinkingOptions = {
* }
* ```
*/
config?: { initialRouteName?: string; screens: PathConfigMap };
config?: {
initialRouteName?: keyof ParamList;
screens: PathConfigMap<ParamList>;
};
/**
* Custom function to get the initial URL used for linking.
* Uses `Linking.getInitialURL()` by default.

View File

@@ -45,7 +45,7 @@ export default function useLinking(
};
},
getStateFromPath = getStateFromPathDefault,
}: LinkingOptions
}: LinkingOptions<ParamListBase>
) {
React.useEffect(() => {
if (enabled !== false && isUsingLinking) {

View File

@@ -295,7 +295,7 @@ export default function useLinking(
config,
getStateFromPath = getStateFromPathDefault,
getPathFromState = getPathFromStateDefault,
}: LinkingOptions
}: LinkingOptions<ParamListBase>
) {
React.useEffect(() => {
if (enabled !== false && isUsingLinking) {