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..b05bb03f3 --- /dev/null +++ b/packages/docs/src/app/playground/_demos/repro-907/page.tsx @@ -0,0 +1,57 @@ +// 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< + Record + >({ + 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 } }