Skip to content

Commit 9912eef

Browse files
committed
fix: Don't rely on URL for initial state
This makes the first render on client side navigation use the source URL rather than the destination, which can cause issues if the first state is memoised or used for some other purposes. useSearchParams contains the destination values for searchParams, however this might break in older versions of Next.js (TBC by CI). In which case this fix might only land on v2.
1 parent 0de6b92 commit 9912eef

File tree

5 files changed

+78
-14
lines changed

5 files changed

+78
-14
lines changed

Diff for: packages/e2e/cypress/e2e/repro-542.cy.js

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/// <reference types="cypress" />
2+
3+
it('Reproduction for issue #542', () => {
4+
cy.visit('/app/repro-542/a?q=foo&r=bar')
5+
cy.contains('#hydration-marker', 'hydrated').should('be.hidden')
6+
cy.get('#q').should('have.text', 'foo')
7+
cy.get('#r').should('have.text', 'bar')
8+
cy.get('a').click()
9+
cy.location('search').should('eq', '')
10+
cy.get('#q').should('have.text', '')
11+
cy.get('#r').should('have.text', '')
12+
cy.get('#initial').should('have.text', '{"q":null,"r":null}')
13+
})

Diff for: packages/e2e/src/app/app/repro-542/a/page.tsx

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
'use client'
2+
3+
import Link from 'next/link'
4+
import { parseAsString, useQueryState, useQueryStates } from 'nuqs'
5+
import { Suspense } from 'react'
6+
7+
export default function Page() {
8+
return (
9+
<Suspense>
10+
<Client />
11+
</Suspense>
12+
)
13+
}
14+
15+
function Client() {
16+
console.log(
17+
'rendering page a, url: %s',
18+
typeof location !== 'undefined' ? location.href : 'ssr'
19+
)
20+
const [q] = useQueryState('q')
21+
const [{ r }] = useQueryStates({ r: parseAsString })
22+
return (
23+
<>
24+
<div id="q">{q}</div>
25+
<div id="r">{r}</div>
26+
<Link href="/app/repro-542/b">Go to page B</Link>
27+
</>
28+
)
29+
}

Diff for: packages/e2e/src/app/app/repro-542/b/page.tsx

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
'use client'
2+
3+
import { parseAsString, useQueryState, useQueryStates } from 'nuqs'
4+
import React, { Suspense } from 'react'
5+
6+
export default function Page() {
7+
return (
8+
<Suspense>
9+
<Client />
10+
</Suspense>
11+
)
12+
}
13+
14+
function Client() {
15+
console.log(
16+
'rendering page b, url: %s',
17+
typeof location !== 'undefined' ? location.href : 'ssr'
18+
)
19+
const ref = React.useRef<any>(null)
20+
const [q] = useQueryState('q')
21+
const [{ r }] = useQueryStates({ r: parseAsString })
22+
if (ref.current === null) {
23+
ref.current = { q, r }
24+
}
25+
return (
26+
<>
27+
<div id="q">{q}</div>
28+
<div id="r">{r}</div>
29+
<div id="initial">{JSON.stringify(ref.current)}</div>
30+
</>
31+
)
32+
}

Diff for: packages/nuqs/src/useQueryState.ts

+1-6
Original file line numberDiff line numberDiff line change
@@ -227,12 +227,7 @@ export function useQueryState<T = string>(
227227
const initialSearchParams = useSearchParams()
228228
const [internalState, setInternalState] = React.useState<T | null>(() => {
229229
const queueValue = getQueuedValue(key)
230-
const urlValue =
231-
typeof location !== 'object'
232-
? // SSR
233-
initialSearchParams?.get(key) ?? null
234-
: // Components mounted after page load must use the current URL value
235-
new URLSearchParams(location.search).get(key) ?? null
230+
const urlValue = initialSearchParams?.get(key) ?? null
236231
const value = queueValue ?? urlValue
237232
return value === null ? null : safeParse(parse, value, key)
238233
})

Diff for: packages/nuqs/src/useQueryStates.ts

+3-8
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,9 @@ export function useQueryStates<KeyMap extends UseQueryStatesKeysMap>(
7272
const router = useRouter()
7373
// Not reactive, but available on the server and on page load
7474
const initialSearchParams = useSearchParams()
75-
const [internalState, setInternalState] = React.useState<V>(() => {
76-
if (typeof location !== 'object') {
77-
// SSR
78-
return parseMap(keyMap, initialSearchParams ?? new URLSearchParams())
79-
}
80-
// Components mounted after page load must use the current URL value
81-
return parseMap(keyMap, new URLSearchParams(location.search))
82-
})
75+
const [internalState, setInternalState] = React.useState<V>(() =>
76+
parseMap(keyMap, initialSearchParams ?? new URLSearchParams())
77+
)
8378
const stateRef = React.useRef(internalState)
8479
debug(
8580
'[nuq+ `%s`] render - state: %O, iSP: %s',

0 commit comments

Comments
 (0)