diff --git a/packages/e2e/next/cypress/e2e/repro-774.cy.js b/packages/e2e/next/cypress/e2e/repro-774.cy.js new file mode 100644 index 000000000..bde2d78d6 --- /dev/null +++ b/packages/e2e/next/cypress/e2e/repro-774.cy.js @@ -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') + }) +}) diff --git a/packages/e2e/next/src/app/app/repro-774/page.tsx b/packages/e2e/next/src/app/app/repro-774/page.tsx new file mode 100644 index 000000000..ed90cdf39 --- /dev/null +++ b/packages/e2e/next/src/app/app/repro-774/page.tsx @@ -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> + </> + ) +} diff --git a/packages/nuqs/src/useQueryStates.ts b/packages/nuqs/src/useQueryStates.ts index 6ece5c74f..849e7315a 100644 --- a/packages/nuqs/src/useQueryStates.ts +++ b/packages/nuqs/src/useQueryStates.ts @@ -133,6 +133,7 @@ export function useQueryStates<KeyMap extends UseQueryStatesKeysMap>( queryRef.current, stateRef.current ) + stateRef.current = state setInternalState(state) }, [ Object.values(resolvedUrlKeys) @@ -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) @@ -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>) }