Skip to content

Commit bf56cb7

Browse files
authored
fix: useQueryStates does not update values correctly when config changes (#908)
1 parent b764322 commit bf56cb7

File tree

3 files changed

+86
-0
lines changed

3 files changed

+86
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// https://github.com/47ng/nuqs/issues/907
2+
3+
'use client'
4+
5+
import { parseAsString, useQueryStates } from 'nuqs'
6+
import { useState } from 'react'
7+
8+
export default function Home() {
9+
const [nuqsConfig, setNuqsConfig] = useState<
10+
Record<string, typeof parseAsString>
11+
>({
12+
p1: parseAsString,
13+
p2: parseAsString
14+
})
15+
16+
const [values] = useQueryStates(nuqsConfig)
17+
18+
return (
19+
<>
20+
<div className="flex gap-2">
21+
<button
22+
onClick={() => setNuqsConfig({ p1: parseAsString })}
23+
className="border p-2"
24+
>
25+
Update config (remove one of the keys)
26+
</button>
27+
28+
<button
29+
onClick={() =>
30+
setNuqsConfig({
31+
p1: parseAsString,
32+
p2: parseAsString,
33+
p3: parseAsString
34+
})
35+
}
36+
className="border p-2"
37+
>
38+
Update config (add a new key)
39+
</button>
40+
41+
<button
42+
onClick={() =>
43+
setNuqsConfig({
44+
p1: parseAsString,
45+
p5: parseAsString
46+
})
47+
}
48+
className="border p-2"
49+
>
50+
Update config (replace a key)
51+
</button>
52+
</div>
53+
<div>Config keys: {JSON.stringify(Object.keys(nuqsConfig))}</div>
54+
<div>Result: {JSON.stringify(values)}</div>
55+
</>
56+
)
57+
}

packages/nuqs/src/useQueryStates.test.ts

+19
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,25 @@ describe('useQueryStates: dynamic keys', () => {
346346
expect(result.current[0].d).toEqual(4)
347347
})
348348

349+
it('updating keys also updates the result structure', () => {
350+
const useTestHook = (keys: string[] = ['a', 'b']) =>
351+
useQueryStates(
352+
keys.reduce((acc, key) => ({ ...acc, [key]: parseAsInteger }), {})
353+
)
354+
const { result, rerender } = renderHook(useTestHook, {
355+
wrapper: withNuqsTestingAdapter({
356+
searchParams: ''
357+
})
358+
})
359+
expect(result.current[0]).toStrictEqual({ a: null, b: null })
360+
rerender(['a']) // remove b
361+
expect(result.current[0]).toStrictEqual({ a: null })
362+
rerender(['a', 'b', 'c']) // add c
363+
expect(result.current[0]).toStrictEqual({ a: null, b: null, c: null })
364+
rerender(['a', 'b', 'd']) // remove c, add d
365+
expect(result.current[0]).toStrictEqual({ a: null, b: null, d: null })
366+
})
367+
349368
it('supports dynamic keys with remapping', () => {
350369
const useTestHook = (keys: [string, string] = ['a', 'b']) =>
351370
useQueryStates(

packages/nuqs/src/useQueryStates.ts

+10
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,16 @@ function parseMap<KeyMap extends UseQueryStatesKeysMap>(
310310
}
311311
return out
312312
}, {} as NullableValues<KeyMap>)
313+
314+
if (!hasChanged) {
315+
// check that keyMap keys have not changed
316+
const keyMapKeys = Object.keys(keyMap)
317+
const cachedStateKeys = Object.keys(cachedState ?? {})
318+
hasChanged =
319+
keyMapKeys.length !== cachedStateKeys.length ||
320+
keyMapKeys.some(key => !cachedStateKeys.includes(key))
321+
}
322+
313323
return { state, hasChanged }
314324
}
315325

0 commit comments

Comments
 (0)