From b28bfddc17cbf3996fac04a34b2a7085ecf88be5 Mon Sep 17 00:00:00 2001 From: Satyajit Sahoo Date: Sun, 9 May 2021 03:44:35 +0200 Subject: [PATCH] feat: add ability to specify root param list --- example/src/index.tsx | 18 +- .../src/__tests__/getPathFromState.test.tsx | 214 +++++++---- .../src/__tests__/getStateFromPath.test.tsx | 350 +++++++++--------- .../core/src/createNavigationContainerRef.tsx | 4 +- packages/core/src/getActionFromState.tsx | 13 +- packages/core/src/getPathFromState.tsx | 13 +- packages/core/src/getStateFromPath.tsx | 23 +- packages/core/src/types.tsx | 29 +- packages/core/src/useNavigation.tsx | 3 +- packages/native/src/LinkingContext.tsx | 3 +- packages/native/src/NavigationContainer.tsx | 18 +- .../src/__tests__/ServerContainer.test.tsx | 38 +- packages/native/src/types.tsx | 7 +- packages/native/src/useLinking.native.tsx | 2 +- packages/native/src/useLinking.tsx | 2 +- 15 files changed, 446 insertions(+), 291 deletions(-) diff --git a/example/src/index.tsx b/example/src/index.tsx index 36dc9afd..f049cbfb 100644 --- a/example/src/index.tsx +++ b/example/src/index.tsx @@ -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; 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( + screens: Object.keys(SCREENS).reduce< + PathConfigMap + >( (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, ' '), }, diff --git a/packages/core/src/__tests__/getPathFromState.test.tsx b/packages/core/src/__tests__/getPathFromState.test.tsx index 5678b636..69fd160e 100644 --- a/packages/core/src/__tests__/getPathFromState.test.tsx +++ b/packages/core/src/__tests__/getPathFromState.test.tsx @@ -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(state)).toBe(path); + expect( + getPathFromState(getStateFromPath(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(state, config)).toBe(path); expect( - getPathFromState(getStateFromPath(path, config) as State, config) + getPathFromState( + getStateFromPath(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(state)).toBe(path); + expect( + getPathFromState(getStateFromPath(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(state)).toBe(path); + expect( + getPathFromState(getStateFromPath(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(state, config)).toBe(path); expect( - getPathFromState(getStateFromPath(path, config) as State, config) + getPathFromState( + getStateFromPath(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(state, config)).toBe(path); expect( - getPathFromState(getStateFromPath(path, config) as State, config) + getPathFromState( + getStateFromPath(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(state, config)).toBe(path); expect( - getPathFromState(getStateFromPath(path, config) as State, config) + getPathFromState( + getStateFromPath(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(state, config)).toBe(path); expect( - getPathFromState(getStateFromPath(path, config) as State, config) + getPathFromState( + getStateFromPath(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(state, config)).toBe(path); expect( - getPathFromState(getStateFromPath(path, config) as State, config) + getPathFromState( + getStateFromPath(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(state, config)).toBe(path); expect( - getPathFromState(getStateFromPath(path, config) as State, config) + getPathFromState( + getStateFromPath(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(state, config)).toBe(path); expect( - getPathFromState(getStateFromPath(path, config) as State, config) + getPathFromState( + getStateFromPath(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(state, config)).toBe(path); expect( - getPathFromState(getStateFromPath(path, config) as State, config) + getPathFromState( + getStateFromPath(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(state, config)).toBe(path); expect( - getPathFromState(getStateFromPath(path, config) as State, config) + getPathFromState( + getStateFromPath(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(state, config)).toBe(path); expect( - getPathFromState(getStateFromPath(path, config) as State, config) + getPathFromState( + getStateFromPath(path, config) as State, + config + ) ).toBe(path); }); @@ -805,9 +844,12 @@ it('ignores empty string paths', () => { ], }; - expect(getPathFromState(state, config)).toBe(path); + expect(getPathFromState(state, config)).toBe(path); expect( - getPathFromState(getStateFromPath(path, config) as State, config) + getPathFromState( + getStateFromPath(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(state, config)).toBe(path); expect( - getPathFromState(getStateFromPath(path, config) as State, config) + getPathFromState( + getStateFromPath(path, config) as State, + config + ) ).toEqual(path); }); @@ -894,9 +939,12 @@ it('cuts nested configs too', () => { ], }; - expect(getPathFromState(state, config)).toBe(path); + expect(getPathFromState(state, config)).toBe(path); expect( - getPathFromState(getStateFromPath(path, config) as State, config) + getPathFromState( + getStateFromPath(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(state, config)).toBe(path); expect( - getPathFromState(getStateFromPath(path, config) as State, config) + getPathFromState( + getStateFromPath(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(state, config)).toBe(path); expect( - getPathFromState(getStateFromPath(path, config) as State, config) + getPathFromState( + getStateFromPath(path, config) as State, + config + ) ).toBe(path); }); @@ -1012,9 +1066,12 @@ it('returns "/" for empty path', () => { ], }; - expect(getPathFromState(state, config)).toBe(path); + expect(getPathFromState(state, config)).toBe(path); expect( - getPathFromState(getStateFromPath(path, config) as State, config) + getPathFromState( + getStateFromPath(path, config) as State, + config + ) ).toBe(path); }); @@ -1042,9 +1099,12 @@ it('parses no path specified', () => { ], }; - expect(getPathFromState(state, config)).toBe(path); + expect(getPathFromState(state, config)).toBe(path); expect( - getPathFromState(getStateFromPath(path, config) as State, config) + getPathFromState( + getStateFromPath(path, config) as State, + config + ) ).toBe(path); }); @@ -1121,9 +1181,12 @@ it('strips undefined query params', () => { ], }; - expect(getPathFromState(state, config)).toBe(path); + expect(getPathFromState(state, config)).toBe(path); expect( - getPathFromState(getStateFromPath(path, config) as State, config) + getPathFromState( + getStateFromPath(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(state, config)).toBe(path); expect( - getPathFromState(getStateFromPath(path, config) as State, config) + getPathFromState( + getStateFromPath(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(state, config)).toBe(path); expect( - getPathFromState(getStateFromPath(path, config) as State, config) + getPathFromState( + getStateFromPath(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(state, config)).toBe(path); expect( - getPathFromState(getStateFromPath(path, config) as State, config) + getPathFromState( + getStateFromPath(path, config) as State, + config + ) ).toBe(path); }); @@ -1380,9 +1452,12 @@ it('replaces undefined query params', () => { ], }; - expect(getPathFromState(state, config)).toBe(path); + expect(getPathFromState(state, config)).toBe(path); expect( - getPathFromState(getStateFromPath(path, config) as State, config) + getPathFromState( + getStateFromPath(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(state, config)).toBe('/404'); expect( - getPathFromState(getStateFromPath(path, config) as State, config) + getPathFromState( + getStateFromPath(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(state, config)).toBe('/bar/42/404'); expect( - getPathFromState(getStateFromPath(path, config) as State, config) + getPathFromState( + getStateFromPath(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(state, config)).toBe('/404'); expect( - getPathFromState(getStateFromPath(path, config) as State, config) + getPathFromState( + getStateFromPath(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(state, config)).toBe(path); expect( - getPathFromState(getStateFromPath(path, config) as State, config) + getPathFromState( + getStateFromPath(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(state, config)).toBe('/404'); expect( - getPathFromState(getStateFromPath(path, config) as State, config) + getPathFromState( + getStateFromPath(path, config) as State, + config + ) ).toBe('/404'); }); diff --git a/packages/core/src/__tests__/getStateFromPath.test.tsx b/packages/core/src/__tests__/getStateFromPath.test.tsx index 5e7a27a3..01cd8312 100644 --- a/packages/core/src/__tests__/getStateFromPath.test.tsx +++ b/packages/core/src/__tests__/getStateFromPath.test.tsx @@ -12,7 +12,7 @@ const changePath = (state: T, path: string): T => }); it('returns undefined for invalid path', () => { - expect(getStateFromPath('//')).toBe(undefined); + expect(getStateFromPath('//')).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(path)).toEqual(state); + expect(getStateFromPath(getPathFromState(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(path, config)).toEqual(state); + expect( + getStateFromPath(getPathFromState(state, config), config) + ).toEqual(state); }); it('handles leading slash when converting', () => { const path = '/foo/bar/?count=42'; - expect(getStateFromPath(path)).toEqual({ + expect(getStateFromPath(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(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(path)).toEqual(state); + expect(getStateFromPath(getPathFromState(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(path, config)).toEqual(state); + expect( + getStateFromPath(getPathFromState(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(path, config)).toEqual(state); + expect( + getStateFromPath(getPathFromState(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(path, config)).toEqual(state); + expect( + getStateFromPath(getPathFromState(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(path, config)).toEqual(state); + expect( + getStateFromPath(getPathFromState(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(path, config)).toEqual(state); + expect( + getStateFromPath(getPathFromState(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(path, config)).toEqual(state); + expect( + getStateFromPath(getPathFromState(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(path, config)).toEqual(state); + expect( + getStateFromPath(getPathFromState(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(path, config)).toEqual(state); + expect( + getStateFromPath(getPathFromState(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(path, config)).toEqual(state); + expect( + getStateFromPath(getPathFromState(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(path, config)).toEqual(state); + expect( + getStateFromPath(getPathFromState(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(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(path, config)).toEqual(state); + expect( + getStateFromPath(getPathFromState(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(path, config)).toEqual(state); + expect( + getStateFromPath(getPathFromState(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(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(path, config)).toEqual(state); + expect( + getStateFromPath(getPathFromState(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(path, config)).toEqual(state); + expect( + getStateFromPath(getPathFromState(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(path, config)).toEqual(state); + expect( + getStateFromPath(getPathFromState(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(path, config)).toEqual(state); + expect( + getStateFromPath(getPathFromState(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(path, config)).toEqual(state); + expect( + getStateFromPath(getPathFromState(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(path, config)).toEqual(state); + expect( + getStateFromPath(getPathFromState(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(path, config)).toEqual(state); + expect( + getStateFromPath(getPathFromState(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(path, config)).toEqual(state); + expect( + getStateFromPath(getPathFromState(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(path, config)).toEqual(state); + expect( + getStateFromPath(getPathFromState(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(path, config)).toEqual(state); + expect( + getStateFromPath(getPathFromState(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(path, config)).toEqual(state); + expect( + getStateFromPath(getPathFromState(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(path, config)).toEqual(state); + expect( + getStateFromPath(getPathFromState(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(path, config)).toEqual(state); + expect( + getStateFromPath(getPathFromState(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(path, config)).toEqual(state); + expect( + getStateFromPath(getPathFromState(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(path, config)).toEqual(state); + expect( + getStateFromPath(getPathFromState(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(path, config)).toEqual(state); + expect( + getStateFromPath(getPathFromState(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(path, config)).toEqual(state); + expect( + getStateFromPath(getPathFromState(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(path, config)).toEqual(state); + expect( + getStateFromPath(getPathFromState(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(path, config)).toEqual(state); + expect( + getStateFromPath(getPathFromState(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(path, config)).toEqual(state); + expect( + getStateFromPath(getPathFromState(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(path, config)).toEqual(state); + expect( + getStateFromPath(getPathFromState(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(path, config)).toEqual(state); + expect( + getStateFromPath(getPathFromState(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(path, config)).toEqual(state); + expect( + getStateFromPath(getPathFromState(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(path, config)).toEqual(state); + expect( + getStateFromPath(getPathFromState(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(path, config)).toEqual(state); + expect( + getStateFromPath(getPathFromState(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(path, config)).toEqual(state); + expect( + getStateFromPath(getPathFromState(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(path, { screens: { Foo: { screens: { @@ -2287,7 +2287,7 @@ it('throws if two screens map to the same pattern', () => { ); expect(() => - getStateFromPath(path, { + getStateFromPath(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(path, config)).toEqual(state); + expect( + getStateFromPath(getPathFromState(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(path, config)).toEqual(state); + expect( + getStateFromPath(getPathFromState(state, config), config) + ).toEqual(state); }); diff --git a/packages/core/src/createNavigationContainerRef.tsx b/packages/core/src/createNavigationContainerRef.tsx index ff07349b..0630e40d 100644 --- a/packages/core/src/createNavigationContainerRef.tsx +++ b/packages/core/src/createNavigationContainerRef.tsx @@ -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 { const methods = [ ...Object.keys(CommonActions), diff --git a/packages/core/src/getActionFromState.tsx b/packages/core/src/getActionFromState.tsx index 8288affe..5b732f40 100644 --- a/packages/core/src/getActionFromState.tsx +++ b/packages/core/src/getActionFromState.tsx @@ -13,7 +13,10 @@ type ConfigItem = { screens?: Record; }; -type Options = { initialRouteName?: string; screens: PathConfigMap }; +type Options = { + initialRouteName?: string; + screens: PathConfigMap; +}; type NavigateAction = { type: 'NAVIGATE'; @@ -29,7 +32,9 @@ export default function getActionFromState( options?: Options ): NavigateAction | 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 | 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 | 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.entries(options).reduce>((acc, [k, v]) => { acc[k] = createNormalizedConfigItem(v); return acc; diff --git a/packages/core/src/getPathFromState.tsx b/packages/core/src/getPathFromState.tsx index 7973ceb9..b3e5295d 100644 --- a/packages/core/src/getPathFromState.tsx +++ b/packages/core/src/getPathFromState.tsx @@ -7,7 +7,10 @@ import type { import fromEntries from './fromEntries'; import type { PathConfig, PathConfigMap } from './types'; -type Options = { initialRouteName?: string; screens: PathConfigMap }; +type Options = { + initialRouteName?: string; + screens: PathConfigMap; +}; type State = NavigationState | Omit, '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( state: State, - options?: Options + options?: Options ): string { if (state == null) { throw Error( @@ -238,7 +241,7 @@ const joinPaths = (...paths: string[]): string => .join('/'); const createConfigItem = ( - config: PathConfig | string, + config: PathConfig | string, parentPattern?: string ): ConfigItem => { if (typeof config === 'string') { @@ -276,7 +279,7 @@ const createConfigItem = ( }; const createNormalizedConfigs = ( - options: PathConfigMap, + options: PathConfigMap, pattern?: string ): Record => fromEntries( diff --git a/packages/core/src/getStateFromPath.tsx b/packages/core/src/getStateFromPath.tsx index 7e11eb5e..6661b9dc 100644 --- a/packages/core/src/getStateFromPath.tsx +++ b/packages/core/src/getStateFromPath.tsx @@ -8,9 +8,9 @@ import type { import findFocusedRoute from './findFocusedRoute'; import type { PathConfigMap } from './types'; -type Options = { +type Options = { initialRouteName?: string; - screens: PathConfigMap; + screens: PathConfigMap; }; type ParseConfig = Record 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( path: string, - options?: Options + options?: Options ): 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, [], initialRoutes, [] @@ -307,7 +307,7 @@ const matchAgainstConfigs = (remaining: string, configs: RouteConfig[]) => { const createNormalizedConfigs = ( screen: string, - routeConfig: PathConfigMap, + routeConfig: PathConfigMap, 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, routeNames, initials, [...parentScreens], diff --git a/packages/core/src/types.tsx b/packages/core/src/types.tsx index 0ae105a3..deb86537 100644 --- a/packages/core/src/types.tsx +++ b/packages/core/src/types.tsx @@ -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 = Extract; export type DefaultNavigatorOptions< @@ -265,7 +273,7 @@ export type NavigationContainerProps = { }; export type NavigationProp< - ParamList extends ParamListBase, + ParamList extends {}, RouteName extends keyof ParamList = Keyof, State extends NavigationState = NavigationState, ScreenOptions extends {} = {}, @@ -510,7 +518,7 @@ export type NavigationContainerEventMap = { }; export type NavigationContainerRef< - ParamList extends ParamListBase + ParamList extends {} > = NavigationHelpers & EventConsumer & { /** @@ -538,7 +546,7 @@ export type NavigationContainerRef< }; export type NavigationContainerRefWithCurrent< - ParamList extends ParamListBase + ParamList extends {} > = NavigationContainerRef & { current: NavigationContainerRef | null; }; @@ -601,15 +609,20 @@ export type NavigatorScreenParams< }; }[keyof ParamList]; -export type PathConfig = { +export type PathConfig = { path?: string; exact?: boolean; parse?: Record any>; stringify?: Record string>; - screens?: PathConfigMap; - initialRouteName?: string; + screens?: PathConfigMap; + initialRouteName?: keyof ParamList; }; -export type PathConfigMap = { - [routeName: string]: string | PathConfig; +export type PathConfigMap = { + [RouteName in keyof ParamList]?: ParamList[RouteName] extends NavigatorScreenParams< + infer T, + any + > + ? string | PathConfig + : string | Omit, 'screens' | 'initialRouteName'>; }; diff --git a/packages/core/src/useNavigation.tsx b/packages/core/src/useNavigation.tsx index 7916e73c..b83b65f4 100644 --- a/packages/core/src/useNavigation.tsx +++ b/packages/core/src/useNavigation.tsx @@ -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 + T extends NavigationProp = NavigationProp >(): T { const navigation = React.useContext(NavigationContext); diff --git a/packages/native/src/LinkingContext.tsx b/packages/native/src/LinkingContext.tsx index 486c5a4e..fa1821be 100644 --- a/packages/native/src/LinkingContext.tsx +++ b/packages/native/src/LinkingContext.tsx @@ -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 | undefined; }>({ options: undefined }); LinkingContext.displayName = 'LinkingContext'; diff --git a/packages/native/src/NavigationContainer.tsx b/packages/native/src/NavigationContainer.tsx index bb63259b..8c46f31c 100644 --- a/packages/native/src/NavigationContainer.tsx +++ b/packages/native/src/NavigationContainer.tsx @@ -14,9 +14,9 @@ import useDocumentTitle from './useDocumentTitle'; import useBackButton from './useBackButton'; import type { Theme, LinkingOptions, DocumentTitleOptions } from './types'; -type Props = NavigationContainerProps & { +type Props = NavigationContainerProps & { theme?: Theme; - linking?: LinkingOptions; + linking?: LinkingOptions; 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, ref?: React.Ref | null> ) { const isLinkingEnabled = linking ? linking.enabled !== false : false; @@ -101,6 +101,14 @@ const NavigationContainer = React.forwardRef(function NavigationContainer( ); -}); +} + +const NavigationContainer = React.forwardRef(NavigationContainerInner) as < + RootParamList extends {} = ReactNavigation.RootParamList +>( + props: Props & { + ref?: React.Ref>; + } +) => React.ReactElement; export default NavigationContainer; diff --git a/packages/native/src/__tests__/ServerContainer.test.tsx b/packages/native/src/__tests__/ServerContainer.test.tsx index 99ac62d9..d55a0ca4 100644 --- a/packages/native/src/__tests__/ServerContainer.test.tsx +++ b/packages/native/src/__tests__/ServerContainer.test.tsx @@ -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; + Chat: undefined; + }; + + type StackBParamList = { + Profile: undefined; + Settings: undefined; + Feed: undefined; + Updates: undefined; + }; + + const StackA = createStackNavigator(); + const StackB = createStackNavigator(); const TestScreen = ({ route }: any): any => `${route.name} ${JSON.stringify(route.params)}`; const NestedStack = () => { return ( - - - - - - + + + + + + ); }; const element = ( - linking={{ prefixes: [], config: { @@ -73,10 +87,10 @@ it('renders correct state with location', () => { }, }} > - - - - + + + + ); diff --git a/packages/native/src/types.tsx b/packages/native/src/types.tsx index 0974f04d..249553a6 100644 --- a/packages/native/src/types.tsx +++ b/packages/native/src/types.tsx @@ -17,7 +17,7 @@ export type Theme = { }; }; -export type LinkingOptions = { +export type LinkingOptions = { /** * 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; + }; /** * Custom function to get the initial URL used for linking. * Uses `Linking.getInitialURL()` by default. diff --git a/packages/native/src/useLinking.native.tsx b/packages/native/src/useLinking.native.tsx index 05c7638e..ad70c5e6 100644 --- a/packages/native/src/useLinking.native.tsx +++ b/packages/native/src/useLinking.native.tsx @@ -45,7 +45,7 @@ export default function useLinking( }; }, getStateFromPath = getStateFromPathDefault, - }: LinkingOptions + }: LinkingOptions ) { React.useEffect(() => { if (enabled !== false && isUsingLinking) { diff --git a/packages/native/src/useLinking.tsx b/packages/native/src/useLinking.tsx index daa9ba6f..1f5083fe 100644 --- a/packages/native/src/useLinking.tsx +++ b/packages/native/src/useLinking.tsx @@ -295,7 +295,7 @@ export default function useLinking( config, getStateFromPath = getStateFromPathDefault, getPathFromState = getPathFromStateDefault, - }: LinkingOptions + }: LinkingOptions ) { React.useEffect(() => { if (enabled !== false && isUsingLinking) {