Skip to content

Commit

Permalink
fix: Update useQueryStates state cache when syncing useSearchParams (#…
Browse files Browse the repository at this point in the history
…776)

This fixes an issue where `<Link>` navigation (or external updates of the URL) kept an internal stale state ref and reused it when updating other search params.
  • Loading branch information
franky47 authored Nov 19, 2024
1 parent 672aa53 commit a504e0e
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 5 deletions.
17 changes: 17 additions & 0 deletions packages/e2e/next/cypress/e2e/repro-774.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/// <reference types="cypress" />

describe('repro-774', () => {
it('updates internal state on navigation', () => {
cy.visit('/app/repro-774')
cy.contains('#hydration-marker', 'hydrated').should('be.hidden')
cy.get('#trigger-a').click()
cy.get('#value-a').should('have.text', 'a')
cy.get('#value-b').should('be.empty')
cy.get('#link').click()
cy.get('#value-a').should('be.empty')
cy.get('#value-b').should('be.empty')
cy.get('#trigger-b').click()
cy.get('#value-a').should('be.empty')
cy.get('#value-b').should('have.text', 'b')
})
})
41 changes: 41 additions & 0 deletions packages/e2e/next/src/app/app/repro-774/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
'use client'

import Link from 'next/link'
import { parseAsString, useQueryStates } from 'nuqs'
import { Suspense } from 'react'

export default function Home() {
return (
<>
<nav>
<Link id="link" href="/app/repro-774">
Reset
</Link>
</nav>
<Suspense>
<Client />
</Suspense>
</>
)
}

const searchParams = {
a: parseAsString.withDefault(''),
b: parseAsString.withDefault('')
}

function Client() {
const [{ a, b }, setSearchParams] = useQueryStates(searchParams)
return (
<>
<button onClick={() => setSearchParams({ a: 'a' })} id="trigger-a">
Set A
</button>
<button onClick={() => setSearchParams({ b: 'b' })} id="trigger-b">
Set B
</button>
<span id="value-a">{a}</span>
<span id="value-b">{b}</span>
</>
)
}
11 changes: 6 additions & 5 deletions packages/nuqs/src/useQueryStates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ export function useQueryStates<KeyMap extends UseQueryStatesKeysMap>(
queryRef.current,
stateRef.current
)
stateRef.current = state
setInternalState(state)
}, [
Object.values(resolvedUrlKeys)
Expand Down Expand Up @@ -274,7 +275,7 @@ function parseMap<KeyMap extends UseQueryStatesKeysMap>(
cachedQuery?: Record<string, string | null>,
cachedState?: NullableValues<KeyMap>
): NullableValues<KeyMap> {
return Object.keys(keyMap).reduce((obj, stateKey) => {
return Object.keys(keyMap).reduce((out, stateKey) => {
const urlKey = urlKeys?.[stateKey] ?? stateKey
const { parse } = keyMap[stateKey]!
const queuedQuery = getQueuedValue(urlKey)
Expand All @@ -283,15 +284,15 @@ function parseMap<KeyMap extends UseQueryStatesKeysMap>(
? (searchParams?.get(urlKey) ?? null)
: queuedQuery
if (cachedQuery && cachedState && cachedQuery[urlKey] === query) {
obj[stateKey as keyof KeyMap] = cachedState[stateKey] ?? null
return obj
out[stateKey as keyof KeyMap] = cachedState[stateKey] ?? null
return out
}
const value = query === null ? null : safeParse(parse, query, stateKey)
obj[stateKey as keyof KeyMap] = value ?? null
out[stateKey as keyof KeyMap] = value ?? null
if (cachedQuery) {
cachedQuery[urlKey] = query
}
return obj
return out
}, {} as NullableValues<KeyMap>)
}

Expand Down

0 comments on commit a504e0e

Please sign in to comment.