Skip to content

Commit 2fba947

Browse files
authored
fix usePageQueryParams accessing stale value (#940)
1 parent 6acad46 commit 2fba947

File tree

5 files changed

+49
-31
lines changed

5 files changed

+49
-31
lines changed

package-lock.json

Lines changed: 5 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@
8787
"jest-environment-jsdom": "^29.7.0",
8888
"jest-fixed-jsdom": "^0.0.8",
8989
"msw": "^2.6.0",
90-
"next-router-mock": "^0.9.13",
90+
"next-router-mock": "^1.0.2",
9191
"pino-pretty": "^11.2.2",
9292
"prettier": "3.2.5",
9393
"styletron-engine-snapshot": "^1.0.2",

src/hooks/use-page-query-params/__tests__/use-page-query-params.test.tsx

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,23 @@
1-
import * as nextRouterMock from 'next-router-mock';
2-
31
import { renderHook, act } from '@/test-utils/rtl';
42

53
import { queryParamsConfig } from '../__fixtures__/page-query-params.fixtures';
64
import usePageQueryParams from '../use-page-query-params';
75

8-
jest.mock('next/router', () => nextRouterMock);
9-
10-
//TODO: @assem.hafez refactor all next/navigations to use the same mock
11-
jest.mock('next/navigation', () => {
12-
const { useRouter } = nextRouterMock;
13-
const usePathname = () => {
14-
const router = useRouter();
15-
return router.pathname;
16-
};
6+
jest.mock('next/navigation', () => require('next-router-mock/navigation'));
177

18-
const useSearchParams = () => {
19-
const router = useRouter();
20-
return new URLSearchParams(router.asPath); // use asPath to get the url as set by our implementation, using router.query or router.search return multivalues in a different format than the actual url
21-
};
8+
describe('usePageQueryParams', () => {
9+
const originalWindowLocation = window.location;
10+
beforeEach(() => {
11+
jest.resetAllMocks();
12+
});
2213

23-
return {
24-
useRouter,
25-
usePathname,
26-
useSearchParams,
27-
};
28-
});
14+
afterEach(() => {
15+
Object.defineProperty(window, 'location', {
16+
value: originalWindowLocation,
17+
writable: true,
18+
});
19+
});
2920

30-
describe('usePageQueryParams', () => {
3121
it('should return default values when search is empty', () => {
3222
const { result } = renderHook(() => usePageQueryParams(queryParamsConfig));
3323
const [values] = result.current;
@@ -40,6 +30,7 @@ describe('usePageQueryParams', () => {
4030
multiValDefaulted: ['a'],
4131
});
4232
});
33+
4334
it('should update values by calling the single key setter method', async () => {
4435
const { result } = renderHook(() => usePageQueryParams(queryParamsConfig));
4536
const [, setValues] = result.current;
@@ -84,4 +75,26 @@ describe('usePageQueryParams', () => {
8475
multiValDefaulted: ['a', 'a'],
8576
});
8677
});
78+
79+
it('should get search values from window.location.search on first client side render', async () => {
80+
Object.defineProperty(window, 'location', {
81+
value: {
82+
...originalWindowLocation,
83+
search: '?sortBy=sortByValue1',
84+
},
85+
writable: true,
86+
});
87+
88+
const { result } = renderHook(() => usePageQueryParams(queryParamsConfig));
89+
const [values] = result.current;
90+
91+
expect(values).toStrictEqual({
92+
sortBy: 'sortByValue1',
93+
aliased: undefined,
94+
defaulted: 'defaultValue',
95+
parsed: undefined,
96+
parsedMultiVal: undefined,
97+
multiValDefaulted: ['a'],
98+
});
99+
});
87100
});

src/hooks/use-page-query-params/use-page-query-params.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,14 @@ export default function usePageQueryParams<P extends PageQueryParams>(
3131
const prevSearchQueryParam = usePreviousValue(searchQueryParams);
3232

3333
const search = useMemo(() => {
34-
// get changed value from searchQueryParams if it was changed
34+
// get changed value from searchQueryParams if it was changed or in case of server side rendering
3535
// otherwise change would be due history state change and search value is available in window.location.search
36-
if (prevSearchQueryParam !== searchQueryParams)
36+
if (
37+
typeof window === 'undefined' ||
38+
(prevSearchQueryParam && prevSearchQueryParam !== searchQueryParams)
39+
)
3740
return searchQueryParams.toString();
41+
3842
return window.location.search;
3943
// stateUrl is needed in deps to recalculate window.location.search
4044
// eslint-disable-next-line react-hooks/exhaustive-deps

src/test-utils/test-provider.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
type QueryClientConfig,
66
QueryClientProvider,
77
} from '@tanstack/react-query';
8-
import { MemoryRouterProvider } from 'next-router-mock/MemoryRouterProvider';
8+
import { MemoryRouterProvider } from 'next-router-mock/MemoryRouterProvider/next-13.5'; // TODO: @assem.hafez remove the dependency on next-router-mock
99
// @ts-expect-error Could not find a declaration file for module 'styletron-engine-snapshot'
1010
import { StyletronSnapshotEngine } from 'styletron-engine-snapshot';
1111

@@ -15,7 +15,7 @@ import StyletronProvider from '@/providers/styletron-provider';
1515
import MSWMockHandlers from './msw-mock-handlers/msw-mock-handlers';
1616
import { type Props } from './test-provider.types';
1717

18-
jest.mock('next/router', () => jest.requireActual('next-router-mock'));
18+
jest.mock('next/router', () => require('next-router-mock'));
1919

2020
const snapshotEngine = new StyletronSnapshotEngine();
2121

0 commit comments

Comments
 (0)