diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 9ae7f4e0..33c36e66 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -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 diff --git a/packages/e2e/cypress.config.ts b/packages/e2e/cypress.config.ts index c452562c..d17c5485 100644 --- a/packages/e2e/cypress.config.ts +++ b/packages/e2e/cypress.config.ts @@ -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' @@ -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') +} diff --git a/packages/e2e/cypress/e2e/useSearchParams.cy.js b/packages/e2e/cypress/e2e/useSearchParams.cy.js new file mode 100644 index 00000000..1e252735 --- /dev/null +++ b/packages/e2e/cypress/e2e/useSearchParams.cy.js @@ -0,0 +1,12 @@ +/// + +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') + }) +} diff --git a/packages/e2e/src/app/app/useSearchParams/page.tsx b/packages/e2e/src/app/app/useSearchParams/page.tsx new file mode 100644 index 00000000..899dc91e --- /dev/null +++ b/packages/e2e/src/app/app/useSearchParams/page.tsx @@ -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 ( + + + + ) +} + +function Client() { + const [q, setQ] = useQueryState('q', { defaultValue: '' }) + const [, setPush] = useQueryState( + 'push', + parseAsBoolean.withDefault(false).withOptions({ history: 'push' }) + ) + const searchParams = useSearchParams() + return ( +
+ setQ(e.target.value)} /> +
{searchParams?.toString() ?? 'null'}
+ +
+ ) +} diff --git a/packages/nuqs/src/update-queue.ts b/packages/nuqs/src/update-queue.ts index 3596b701..c16a34a6 100644 --- a/packages/nuqs/src/update-queue.ts +++ b/packages/nuqs/src/update-queue.ts @@ -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 @@ -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 }) }) } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9eb86ef0..7f14a340 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3840,18 +3840,6 @@ packages: supports-color: 8.1.1 dev: true - /debug@4.3.4: - resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - dependencies: - ms: 2.1.2 - dev: false - /debug@4.3.4(supports-color@8.1.1): resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} engines: {node: '>=6.0'} @@ -3863,7 +3851,6 @@ packages: dependencies: ms: 2.1.2 supports-color: 8.1.1 - dev: true /decamelize-keys@1.1.1: resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} @@ -4636,7 +4623,6 @@ packages: /has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} - dev: true /hast-util-from-html@2.0.1: resolution: {integrity: sha512-RXQBLMl9kjKVNkJTIO6bZyb2n+cUH8LFaSSzo82jiLT6Tfc+Pt7VQCS+/h3YwG4jaNE2TA2sdJisGWR+aJrp0g==} @@ -6052,7 +6038,7 @@ packages: resolution: {integrity: sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==} dependencies: '@types/debug': 4.1.12 - debug: 4.3.4 + debug: 4.3.4(supports-color@8.1.1) decode-named-character-reference: 1.0.2 devlop: 1.1.0 micromark-core-commonmark: 2.0.0 @@ -8048,7 +8034,6 @@ packages: engines: {node: '>=10'} dependencies: has-flag: 4.0.0 - dev: true /supports-hyperlinks@2.3.0: resolution: {integrity: sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==}