Skip to content

Commit

Permalink
fix: Sync with useSearchParams in 14.1.0
Browse files Browse the repository at this point in the history
* fix: Set the history state to null

As recommended in the Next.js docs for shallow routing.

Historically, while Next.js didn't support shallow routing (<14.1.0),
maintaining the history state was the way to keep
the app router state.

* chore: Remove dead code

No need to pass pages-router specific code here,
as this branch now only runs in the app router context.

* fix: Quick fix attempt for older versions

* chore: Fence test and add push check

* chore: Disable 14.0.5-canary.54 in CI

The fix only applies to GA versions.

* chore: Update comment for later reference

* chore: Restore test against 14.0.4

* chore: Update comment
  • Loading branch information
franky47 authored Feb 1, 2024
1 parent 25356b8 commit 50da148
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 32 deletions.
13 changes: 5 additions & 8 deletions .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,15 @@ jobs:
- '13.5'
- '14.0.1'
# 14.0.2 is not compatible due to a prefetch issue
- latest
- '14.0.5-canary.54' # WHS stabilised
# 14.0.3 requires the WHS flag (see below)
- '14.0.4'
- latest # Current latest is 14.1.0
include:
# 14.0.3 requires the WHS flag
- next-version: '14.0.3'
window-history-support: true
# Current latest is 14.0.4
- next-version: latest
# 14.0.4 doesn't require the WHS flag, but supports it
- next-version: '14.0.4'
window-history-support: true
- next-version: latest
window-history-support: true
base-path: '/base'

steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
Expand Down
18 changes: 14 additions & 4 deletions packages/e2e/cypress.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import semver from 'semver'
const basePath =
process.env.BASE_PATH === '/' ? '' : process.env.BASE_PATH ?? ''

const windowHistorySupport = supportsWHS()
const nextJsVersion = readNextJsVersion()

const windowHistorySupport = supportsWHS(nextJsVersion)
? process.env.WINDOW_HISTORY_SUPPORT === 'true'
? 'true'
: 'false'
Expand All @@ -21,17 +23,25 @@ export default defineConfig({
retries: 2,
env: {
basePath,
windowHistorySupport
windowHistorySupport,
supportsShallowRouting: supportsShallowRouting(nextJsVersion)
}
}
})

function supportsWHS() {
function readNextJsVersion() {
const pkgPath = new URL('./package.json', import.meta.url)
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
const nextVersion: string = pkg.dependencies.next
return pkg.dependencies.next
}

function supportsWHS(nextVersion: string) {
return (
semver.gte(nextVersion, '14.0.3-canary.6') &&
semver.lt(nextVersion, '14.0.5-canary.54')
)
}

function supportsShallowRouting(nextVersion: string) {
return semver.gte(nextVersion, '14.1.0')
}
12 changes: 12 additions & 0 deletions packages/e2e/cypress/e2e/useSearchParams.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/// <reference types="cypress" />

if (Cypress.env('supportsShallowRouting')) {
it('useSearchParams', () => {
cy.visit('/app/useSearchParams')
cy.contains('#hydration-marker', 'hydrated').should('be.hidden')
cy.get('input').type('foo', { delay: 0 })
cy.get('#searchParams').should('have.text', 'q=foo')
cy.get('button').click()
cy.get('#searchParams').should('have.text', 'q=foo&push=true')
})
}
29 changes: 29 additions & 0 deletions packages/e2e/src/app/app/useSearchParams/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use client'

import { useSearchParams } from 'next/navigation'
import { parseAsBoolean, useQueryState } from 'nuqs'
import { Suspense } from 'react'

export default function Page() {
return (
<Suspense>
<Client />
</Suspense>
)
}

function Client() {
const [q, setQ] = useQueryState('q', { defaultValue: '' })
const [, setPush] = useQueryState(
'push',
parseAsBoolean.withDefault(false).withOptions({ history: 'push' })
)
const searchParams = useSearchParams()
return (
<div>
<input type="text" value={q} onChange={e => setQ(e.target.value)} />
<pre id="searchParams">{searchParams?.toString() ?? 'null'}</pre>
<button onClick={() => setPush(true)}>Push</button>
</div>
)
}
13 changes: 9 additions & 4 deletions packages/nuqs/src/update-queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,9 +180,16 @@ function flushUpdateQueue(router: Router): [URLSearchParams, null | unknown] {
// this allows keeping a reactive URL if the network is slow.
const updateMethod =
options.history === 'push' ? history.pushState : history.replaceState
// In 14.1.0, useSearchParams becomes reactive to shallow updates,
// but only if passing `null` as the history state.
// Older versions need to maintain the history state for push to work.
// This should theoretically be checking for >=14.0.5-canary.54 where WHS
// was stabilised, but we're not supporting canaries from previous GAs.
const state =
(window.next?.version ?? '') >= '14.1.0' ? null : history.state
updateMethod.call(
history,
history.state,
state,
// Our own updates have a marker to prevent syncing
// when the URL changes (we've already sync'd them up
// via `emitter.emit(key, newValue)` above, without
Expand All @@ -198,9 +205,7 @@ function flushUpdateQueue(router: Router): [URLSearchParams, null | unknown] {
// Call the Next.js router to perform a network request
// and re-render server components.
router.replace(url, {
scroll: false,
// @ts-expect-error - pages router fix, but not exposed in navigation types
shallow: false
scroll: false
})
})
}
Expand Down
17 changes: 1 addition & 16 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 50da148

Please sign in to comment.