From d926cf762bfde44746fcdc1c356934f596835ec5 Mon Sep 17 00:00:00 2001 From: Yehor Kharchenko Date: Thu, 12 Mar 2026 14:32:30 +0100 Subject: [PATCH 1/3] add parameters propagating to dynamic routes state --- .../getStateForDynamicRoute.ts | 9 +++--- .../Navigation/helpers/getStateFromPath.ts | 2 +- .../getStateForDynamicRouteTests.ts | 30 +++++++++++++++++++ tests/navigation/getStateFromPathTests.ts | 5 ++-- 4 files changed, 39 insertions(+), 7 deletions(-) diff --git a/src/libs/Navigation/helpers/dynamicRoutesUtils/getStateForDynamicRoute.ts b/src/libs/Navigation/helpers/dynamicRoutesUtils/getStateForDynamicRoute.ts index ce35dc567c20a..4ac21c3aabf8c 100644 --- a/src/libs/Navigation/helpers/dynamicRoutesUtils/getStateForDynamicRoute.ts +++ b/src/libs/Navigation/helpers/dynamicRoutesUtils/getStateForDynamicRoute.ts @@ -6,7 +6,7 @@ import splitPathAndQuery from './splitPathAndQuery'; type LeafRoute = { name: string; path: string; - params?: Record; + params?: Record; }; type NestedRoute = { @@ -54,7 +54,7 @@ function getRouteNamesForDynamicRoute(dynamicRouteName: DynamicRouteSuffix): str return null; } -function getStateForDynamicRoute(path: string, dynamicRouteName: keyof typeof DYNAMIC_ROUTES) { +function getStateForDynamicRoute(path: string, dynamicRouteName: keyof typeof DYNAMIC_ROUTES, parentRouteParams?: Record) { const routeConfig = getRouteNamesForDynamicRoute(DYNAMIC_ROUTES[dynamicRouteName].path); const [, query] = splitPathAndQuery(path); const params = getParamsFromQuery(query); @@ -67,12 +67,13 @@ function getStateForDynamicRoute(path: string, dynamicRouteName: keyof typeof DY const buildNestedState = (routes: string[], currentIndex: number): RouteNode => { const currentRoute = routes.at(currentIndex); - // If this is the last route, create leaf node with path + // If this is the last route, create leaf node with path and merged params if (currentIndex === routes.length - 1) { + const mergedParams = parentRouteParams || params ? {...parentRouteParams, ...params} : undefined; return { name: currentRoute ?? '', path, - params, + ...(mergedParams ? {params: mergedParams} : {}), }; } diff --git a/src/libs/Navigation/helpers/getStateFromPath.ts b/src/libs/Navigation/helpers/getStateFromPath.ts index 92119fdbf2281..2c4d7a11dcce2 100644 --- a/src/libs/Navigation/helpers/getStateFromPath.ts +++ b/src/libs/Navigation/helpers/getStateFromPath.ts @@ -39,7 +39,7 @@ function getStateFromPath(path: Route): PartialState { if (focusedRoute?.name) { if (entryScreens.includes(focusedRoute.name as Screen)) { // Generate navigation state for the dynamic route - const dynamicRouteState = getStateForDynamicRoute(normalizedPath, dynamicRoute as DynamicRouteKey); + const dynamicRouteState = getStateForDynamicRoute(normalizedPath, dynamicRoute as DynamicRouteKey, focusedRoute?.params as Record | undefined); return dynamicRouteState; } diff --git a/tests/navigation/getStateForDynamicRouteTests.ts b/tests/navigation/getStateForDynamicRouteTests.ts index cf3dc7c588f0e..9e2669201418f 100644 --- a/tests/navigation/getStateForDynamicRouteTests.ts +++ b/tests/navigation/getStateForDynamicRouteTests.ts @@ -31,6 +31,7 @@ jest.mock('@src/ROUTES', () => ({ type LeafRoute = { name: string; path: string; + params?: Record; }; type NestedRoute = { @@ -111,4 +112,33 @@ describe('getStateForDynamicRoute', () => { expect(state?.index).toBe(0); expect(Array.isArray(state?.routes)).toBe(true); }); + + it('should inherit parent route params on the leaf node', () => { + const path = '/r/12345/settings/test-path'; + const parentParams = {reportID: '12345'}; + const result = getStateForDynamicRoute(path, KEY_TEST as unknown as keyof typeof DYNAMIC_ROUTES, parentParams); + + const rootRoute = result.routes.at(0) as NestedRoute | undefined; + const leafRoute = rootRoute?.state.routes.at(0) as LeafRoute | undefined; + expect(leafRoute?.params).toEqual(parentParams); + }); + + it('should not include params on the leaf node when neither parentRouteParams nor query params are provided', () => { + const path = '/some/path/test-path'; + const result = getStateForDynamicRoute(path, KEY_TEST as unknown as keyof typeof DYNAMIC_ROUTES); + + const rootRoute = result.routes.at(0) as NestedRoute | undefined; + const leafRoute = rootRoute?.state.routes.at(0) as LeafRoute | undefined; + expect(leafRoute?.params).toBeUndefined(); + }); + + it('should merge parent route params with query params', () => { + const path = '/r/12345/settings/test-path?country=US'; + const parentParams = {reportID: '12345'}; + const result = getStateForDynamicRoute(path, KEY_TEST as unknown as keyof typeof DYNAMIC_ROUTES, parentParams); + + const rootRoute = result.routes.at(0) as NestedRoute | undefined; + const leafRoute = rootRoute?.state.routes.at(0) as LeafRoute | undefined; + expect(leafRoute?.params).toEqual({reportID: '12345', country: 'US'}); + }); }); diff --git a/tests/navigation/getStateFromPathTests.ts b/tests/navigation/getStateFromPathTests.ts index ffa5cb2ace840..5808b6f164e4d 100644 --- a/tests/navigation/getStateFromPathTests.ts +++ b/tests/navigation/getStateFromPathTests.ts @@ -57,9 +57,10 @@ describe('getStateFromPath', () => { it('should generate dynamic state when authorized screen is focused', () => { const fullPath = '/settings/wallet/verify-account'; const baseRouteState = {routes: [{name: 'Wallet'}]}; + const focusedRouteParams = {walletID: '456'}; mockRNGetStateFromPath.mockReturnValue(baseRouteState); - mockFindFocusedRoute.mockReturnValue({name: 'Wallet'}); + mockFindFocusedRoute.mockReturnValue({name: 'Wallet', params: focusedRouteParams}); const expectedDynamicState = {routes: [{name: 'DynamicRoot'}]}; mockGetStateForDynamicRoute.mockReturnValue(expectedDynamicState); @@ -67,7 +68,7 @@ describe('getStateFromPath', () => { const result = getStateFromPath(fullPath as unknown as Route); expect(result).toBe(expectedDynamicState); - expect(mockGetStateForDynamicRoute).toHaveBeenCalledWith(fullPath, 'VERIFY_ACCOUNT'); + expect(mockGetStateForDynamicRoute).toHaveBeenCalledWith(fullPath, 'VERIFY_ACCOUNT', focusedRouteParams); }); it('should fallback to standard RN parsing if focused screen is NOT authorized for dynamic route', () => { From 961418f5e19283ed7f8cfb2ecc991fbab877f53a Mon Sep 17 00:00:00 2001 From: Yehor Kharchenko Date: Thu, 12 Mar 2026 16:33:52 +0100 Subject: [PATCH 2/3] cleanup --- .../helpers/dynamicRoutesUtils/getStateForDynamicRoute.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/Navigation/helpers/dynamicRoutesUtils/getStateForDynamicRoute.ts b/src/libs/Navigation/helpers/dynamicRoutesUtils/getStateForDynamicRoute.ts index 4ac21c3aabf8c..32531ade90f55 100644 --- a/src/libs/Navigation/helpers/dynamicRoutesUtils/getStateForDynamicRoute.ts +++ b/src/libs/Navigation/helpers/dynamicRoutesUtils/getStateForDynamicRoute.ts @@ -70,10 +70,11 @@ function getStateForDynamicRoute(path: string, dynamicRouteName: keyof typeof DY // If this is the last route, create leaf node with path and merged params if (currentIndex === routes.length - 1) { const mergedParams = parentRouteParams || params ? {...parentRouteParams, ...params} : undefined; + const paramsSpread = mergedParams ? {params: mergedParams} : {}; return { name: currentRoute ?? '', path, - ...(mergedParams ? {params: mergedParams} : {}), + ...paramsSpread, }; } From 88b433792bfc2dbe1bc4329a28d9b94309d8d517 Mon Sep 17 00:00:00 2001 From: Yehor Kharchenko Date: Fri, 13 Mar 2026 16:10:04 +0100 Subject: [PATCH 3/3] ensure that params aren't undefined --- .../helpers/dynamicRoutesUtils/getStateForDynamicRoute.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Navigation/helpers/dynamicRoutesUtils/getStateForDynamicRoute.ts b/src/libs/Navigation/helpers/dynamicRoutesUtils/getStateForDynamicRoute.ts index 32531ade90f55..882b4a8f59c9f 100644 --- a/src/libs/Navigation/helpers/dynamicRoutesUtils/getStateForDynamicRoute.ts +++ b/src/libs/Navigation/helpers/dynamicRoutesUtils/getStateForDynamicRoute.ts @@ -69,7 +69,7 @@ function getStateForDynamicRoute(path: string, dynamicRouteName: keyof typeof DY // If this is the last route, create leaf node with path and merged params if (currentIndex === routes.length - 1) { - const mergedParams = parentRouteParams || params ? {...parentRouteParams, ...params} : undefined; + const mergedParams = parentRouteParams || params ? {...(parentRouteParams ?? {}), ...(params ?? {})} : undefined; const paramsSpread = mergedParams ? {params: mergedParams} : {}; return { name: currentRoute ?? '',