Skip to content

Commit

Permalink
fix: useQueryStates does not update values correctly when config chan…
Browse files Browse the repository at this point in the history
…ges (#908)
  • Loading branch information
maslianok authored Feb 12, 2025
1 parent b764322 commit bf56cb7
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 0 deletions.
57 changes: 57 additions & 0 deletions packages/docs/src/app/playground/_demos/repro-907/page.tsx
Original file line number Diff line number Diff line change
@@ -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<string, typeof parseAsString>
>({
p1: parseAsString,
p2: parseAsString
})

const [values] = useQueryStates(nuqsConfig)

return (
<>
<div className="flex gap-2">
<button
onClick={() => setNuqsConfig({ p1: parseAsString })}
className="border p-2"
>
Update config (remove one of the keys)
</button>

<button
onClick={() =>
setNuqsConfig({
p1: parseAsString,
p2: parseAsString,
p3: parseAsString
})
}
className="border p-2"
>
Update config (add a new key)
</button>

<button
onClick={() =>
setNuqsConfig({
p1: parseAsString,
p5: parseAsString
})
}
className="border p-2"
>
Update config (replace a key)
</button>
</div>
<div>Config keys: {JSON.stringify(Object.keys(nuqsConfig))}</div>
<div>Result: {JSON.stringify(values)}</div>
</>
)
}
19 changes: 19 additions & 0 deletions packages/nuqs/src/useQueryStates.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
10 changes: 10 additions & 0 deletions packages/nuqs/src/useQueryStates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,16 @@ function parseMap<KeyMap extends UseQueryStatesKeysMap>(
}
return out
}, {} as NullableValues<KeyMap>)

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 }
}

Expand Down

0 comments on commit bf56cb7

Please sign in to comment.