Skip to content

Commit 68a869e

Browse files
authored
Merge pull request Expensify#85079 from software-mansion-labs/collectioneur/dynamic-routes-parameters-propagating
2 parents 3dff0d0 + 88b4337 commit 68a869e

4 files changed

Lines changed: 40 additions & 7 deletions

File tree

src/libs/Navigation/helpers/dynamicRoutesUtils/getStateForDynamicRoute.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import splitPathAndQuery from './splitPathAndQuery';
66
type LeafRoute = {
77
name: string;
88
path: string;
9-
params?: Record<string, string>;
9+
params?: Record<string, unknown>;
1010
};
1111

1212
type NestedRoute = {
@@ -54,7 +54,7 @@ function getRouteNamesForDynamicRoute(dynamicRouteName: DynamicRouteSuffix): str
5454
return null;
5555
}
5656

57-
function getStateForDynamicRoute(path: string, dynamicRouteName: keyof typeof DYNAMIC_ROUTES) {
57+
function getStateForDynamicRoute(path: string, dynamicRouteName: keyof typeof DYNAMIC_ROUTES, parentRouteParams?: Record<string, unknown>) {
5858
const routeConfig = getRouteNamesForDynamicRoute(DYNAMIC_ROUTES[dynamicRouteName].path);
5959
const [, query] = splitPathAndQuery(path);
6060
const params = getParamsFromQuery(query);
@@ -67,12 +67,14 @@ function getStateForDynamicRoute(path: string, dynamicRouteName: keyof typeof DY
6767
const buildNestedState = (routes: string[], currentIndex: number): RouteNode => {
6868
const currentRoute = routes.at(currentIndex);
6969

70-
// If this is the last route, create leaf node with path
70+
// If this is the last route, create leaf node with path and merged params
7171
if (currentIndex === routes.length - 1) {
72+
const mergedParams = parentRouteParams || params ? {...(parentRouteParams ?? {}), ...(params ?? {})} : undefined;
73+
const paramsSpread = mergedParams ? {params: mergedParams} : {};
7274
return {
7375
name: currentRoute ?? '',
7476
path,
75-
params,
77+
...paramsSpread,
7678
};
7779
}
7880

src/libs/Navigation/helpers/getStateFromPath.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ function getStateFromPath(path: Route): PartialState<NavigationState> {
3939
if (focusedRoute?.name) {
4040
if (entryScreens.includes(focusedRoute.name as Screen)) {
4141
// Generate navigation state for the dynamic route
42-
const dynamicRouteState = getStateForDynamicRoute(normalizedPath, dynamicRoute as DynamicRouteKey);
42+
const dynamicRouteState = getStateForDynamicRoute(normalizedPath, dynamicRoute as DynamicRouteKey, focusedRoute?.params as Record<string, unknown> | undefined);
4343
return dynamicRouteState;
4444
}
4545

tests/navigation/getStateForDynamicRouteTests.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ jest.mock('@src/ROUTES', () => ({
3131
type LeafRoute = {
3232
name: string;
3333
path: string;
34+
params?: Record<string, unknown>;
3435
};
3536

3637
type NestedRoute = {
@@ -111,4 +112,33 @@ describe('getStateForDynamicRoute', () => {
111112
expect(state?.index).toBe(0);
112113
expect(Array.isArray(state?.routes)).toBe(true);
113114
});
115+
116+
it('should inherit parent route params on the leaf node', () => {
117+
const path = '/r/12345/settings/test-path';
118+
const parentParams = {reportID: '12345'};
119+
const result = getStateForDynamicRoute(path, KEY_TEST as unknown as keyof typeof DYNAMIC_ROUTES, parentParams);
120+
121+
const rootRoute = result.routes.at(0) as NestedRoute | undefined;
122+
const leafRoute = rootRoute?.state.routes.at(0) as LeafRoute | undefined;
123+
expect(leafRoute?.params).toEqual(parentParams);
124+
});
125+
126+
it('should not include params on the leaf node when neither parentRouteParams nor query params are provided', () => {
127+
const path = '/some/path/test-path';
128+
const result = getStateForDynamicRoute(path, KEY_TEST as unknown as keyof typeof DYNAMIC_ROUTES);
129+
130+
const rootRoute = result.routes.at(0) as NestedRoute | undefined;
131+
const leafRoute = rootRoute?.state.routes.at(0) as LeafRoute | undefined;
132+
expect(leafRoute?.params).toBeUndefined();
133+
});
134+
135+
it('should merge parent route params with query params', () => {
136+
const path = '/r/12345/settings/test-path?country=US';
137+
const parentParams = {reportID: '12345'};
138+
const result = getStateForDynamicRoute(path, KEY_TEST as unknown as keyof typeof DYNAMIC_ROUTES, parentParams);
139+
140+
const rootRoute = result.routes.at(0) as NestedRoute | undefined;
141+
const leafRoute = rootRoute?.state.routes.at(0) as LeafRoute | undefined;
142+
expect(leafRoute?.params).toEqual({reportID: '12345', country: 'US'});
143+
});
114144
});

tests/navigation/getStateFromPathTests.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,17 +57,18 @@ describe('getStateFromPath', () => {
5757
it('should generate dynamic state when authorized screen is focused', () => {
5858
const fullPath = '/settings/wallet/verify-account';
5959
const baseRouteState = {routes: [{name: 'Wallet'}]};
60+
const focusedRouteParams = {walletID: '456'};
6061

6162
mockRNGetStateFromPath.mockReturnValue(baseRouteState);
62-
mockFindFocusedRoute.mockReturnValue({name: 'Wallet'});
63+
mockFindFocusedRoute.mockReturnValue({name: 'Wallet', params: focusedRouteParams});
6364

6465
const expectedDynamicState = {routes: [{name: 'DynamicRoot'}]};
6566
mockGetStateForDynamicRoute.mockReturnValue(expectedDynamicState);
6667

6768
const result = getStateFromPath(fullPath as unknown as Route);
6869

6970
expect(result).toBe(expectedDynamicState);
70-
expect(mockGetStateForDynamicRoute).toHaveBeenCalledWith(fullPath, 'VERIFY_ACCOUNT');
71+
expect(mockGetStateForDynamicRoute).toHaveBeenCalledWith(fullPath, 'VERIFY_ACCOUNT', focusedRouteParams);
7172
});
7273

7374
it('should fallback to standard RN parsing if focused screen is NOT authorized for dynamic route', () => {

0 commit comments

Comments
 (0)