From 072f83f51f56799e48441f67cd68cee7da178eb1 Mon Sep 17 00:00:00 2001 From: maslianok Date: Wed, 12 Feb 2025 12:56:24 +0100 Subject: [PATCH 1/2] fix: useQueryStates does not update values correctly when config --- .../app/playground/_demos/repro-907/page.tsx | 55 +++++++++++++++++++ packages/nuqs/src/useQueryStates.test.ts | 19 +++++++ packages/nuqs/src/useQueryStates.ts | 10 ++++ 3 files changed, 84 insertions(+) create mode 100644 packages/docs/src/app/playground/_demos/repro-907/page.tsx diff --git a/packages/docs/src/app/playground/_demos/repro-907/page.tsx b/packages/docs/src/app/playground/_demos/repro-907/page.tsx new file mode 100644 index 000000000..313e7fb91 --- /dev/null +++ b/packages/docs/src/app/playground/_demos/repro-907/page.tsx @@ -0,0 +1,55 @@ +// https://github.com/47ng/nuqs/issues/907 + +'use client' + +import { parseAsString, useQueryStates } from 'nuqs' +import { useState } from 'react' + +export default function Home() { + const [nuqsConfig, setNuqsConfig] = useState({ + p1: parseAsString, + p2: parseAsString + }) + + const [values] = useQueryStates(nuqsConfig) + + return ( + <> +
+ + + + + +
+
Config keys: {JSON.stringify(Object.keys(nuqsConfig))}
+
Result: {JSON.stringify(values)}
+ + ) +} diff --git a/packages/nuqs/src/useQueryStates.test.ts b/packages/nuqs/src/useQueryStates.test.ts index 37674a6f6..5359920b2 100644 --- a/packages/nuqs/src/useQueryStates.test.ts +++ b/packages/nuqs/src/useQueryStates.test.ts @@ -346,6 +346,25 @@ describe('useQueryStates: dynamic keys', () => { expect(result.current[0].d).toEqual(4) }) + it('updating keys also updates the result structure', () => { + const useTestHook = (keys: string[] = ['a', 'b']) => + useQueryStates( + keys.reduce((acc, key) => ({ ...acc, [key]: parseAsInteger }), {}) + ) + const { result, rerender } = renderHook(useTestHook, { + wrapper: withNuqsTestingAdapter({ + searchParams: '' + }) + }) + expect(result.current[0]).toStrictEqual({ a: null, b: null }) + rerender(['a']) // remove b + expect(result.current[0]).toStrictEqual({ a: null }) + rerender(['a', 'b', 'c']) // add c + expect(result.current[0]).toStrictEqual({ a: null, b: null, c: null }) + rerender(['a', 'b', 'd']) // remove c, add d + expect(result.current[0]).toStrictEqual({ a: null, b: null, d: null }) + }) + it('supports dynamic keys with remapping', () => { const useTestHook = (keys: [string, string] = ['a', 'b']) => useQueryStates( diff --git a/packages/nuqs/src/useQueryStates.ts b/packages/nuqs/src/useQueryStates.ts index 2a56be2ab..112f944f9 100644 --- a/packages/nuqs/src/useQueryStates.ts +++ b/packages/nuqs/src/useQueryStates.ts @@ -310,6 +310,16 @@ function parseMap( } return out }, {} as NullableValues) + + if (!hasChanged) { + // check that keyMap keys have not changed + const keyMapKeys = Object.keys(keyMap) + const cachedStateKeys = Object.keys(cachedState ?? {}) + hasChanged = + keyMapKeys.length !== cachedStateKeys.length || + keyMapKeys.some(key => !cachedStateKeys.includes(key)) + } + return { state, hasChanged } } From 5c485e7fd4c73f8bfc38d1247db60251d21601fa Mon Sep 17 00:00:00 2001 From: maslianok Date: Wed, 12 Feb 2025 15:11:21 +0100 Subject: [PATCH 2/2] fix: resolve comments --- packages/docs/src/app/playground/_demos/repro-907/page.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/docs/src/app/playground/_demos/repro-907/page.tsx b/packages/docs/src/app/playground/_demos/repro-907/page.tsx index 313e7fb91..b05bb03f3 100644 --- a/packages/docs/src/app/playground/_demos/repro-907/page.tsx +++ b/packages/docs/src/app/playground/_demos/repro-907/page.tsx @@ -6,7 +6,9 @@ import { parseAsString, useQueryStates } from 'nuqs' import { useState } from 'react' export default function Home() { - const [nuqsConfig, setNuqsConfig] = useState({ + const [nuqsConfig, setNuqsConfig] = useState< + Record + >({ p1: parseAsString, p2: parseAsString })