diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml
index 3ff3517ee..16b404310 100644
--- a/.github/workflows/ci-cd.yml
+++ b/.github/workflows/ci-cd.yml
@@ -29,6 +29,7 @@ jobs:
- '14.0.1'
# 14.0.2 is not compatible due to a prefetch issue
- latest
+ - '14.0.5-canary.54' # WHS stabilised
include:
# 14.0.3 requires the WHS flag
- next-version: '14.0.3'
diff --git a/packages/e2e/cypress/e2e/repro-359.cy.js b/packages/e2e/cypress/e2e/repro-359.cy.js
new file mode 100644
index 000000000..4600f912b
--- /dev/null
+++ b/packages/e2e/cypress/e2e/repro-359.cy.js
@@ -0,0 +1,52 @@
+///
+
+it.only('repro-359', () => {
+ cy.visit('/app/repro-359')
+ cy.contains('#hydration-marker', 'hydrated').should('be.hidden')
+
+ cy.location('search').should('eq', '')
+ cy.get('#nuqs-param').should('have.text', 'null')
+ cy.get('#nuqs-component').should('have.text', '')
+ cy.get('#nuqss-param').should('have.text', 'null')
+ cy.get('#nuqss-component').should('have.text', '')
+
+ cy.contains('Component 1 (nuqs)').click()
+ cy.wait(100)
+ cy.location('search').should('eq', '?param=comp1&component=comp1')
+ cy.get('#comp1').should('have.text', 'comp1')
+ cy.get('#comp2').should('not.exist')
+ cy.get('#nuqs-param').should('have.text', 'comp1')
+ cy.get('#nuqs-component').should('have.text', 'comp1')
+ cy.get('#nuqss-param').should('have.text', 'comp1')
+ cy.get('#nuqss-component').should('have.text', 'comp1')
+
+ cy.contains('Component 2 (nuqs)').click()
+ cy.wait(100)
+ cy.location('search').should('eq', '?param=comp2&component=comp2')
+ cy.get('#comp1').should('not.exist')
+ cy.get('#comp2').should('have.text', 'comp2')
+ cy.get('#nuqs-param').should('have.text', 'comp2')
+ cy.get('#nuqs-component').should('have.text', 'comp2')
+ cy.get('#nuqss-param').should('have.text', 'comp2')
+ cy.get('#nuqss-component').should('have.text', 'comp2')
+
+ cy.contains('Component 1 (nuq+)').click()
+ cy.wait(100)
+ cy.location('search').should('eq', '?param=comp1&component=comp1')
+ cy.get('#comp1').should('have.text', 'comp1')
+ cy.get('#comp2').should('not.exist')
+ cy.get('#nuqs-param').should('have.text', 'comp1')
+ cy.get('#nuqs-component').should('have.text', 'comp1')
+ cy.get('#nuqss-param').should('have.text', 'comp1')
+ cy.get('#nuqss-component').should('have.text', 'comp1')
+
+ cy.contains('Component 2 (nuq+)').click()
+ cy.wait(100)
+ cy.location('search').should('eq', '?param=comp2&component=comp2')
+ cy.get('#comp1').should('not.exist')
+ cy.get('#comp2').should('have.text', 'comp2')
+ cy.get('#nuqs-param').should('have.text', 'comp2')
+ cy.get('#nuqs-component').should('have.text', 'comp2')
+ cy.get('#nuqss-param').should('have.text', 'comp2')
+ cy.get('#nuqss-component').should('have.text', 'comp2')
+})
diff --git a/packages/e2e/src/app/app/repro-359/page.tsx b/packages/e2e/src/app/app/repro-359/page.tsx
new file mode 100644
index 000000000..2234d7746
--- /dev/null
+++ b/packages/e2e/src/app/app/repro-359/page.tsx
@@ -0,0 +1,86 @@
+// https://github.com/47ng/nuqs/issues/359
+
+'use client'
+
+import Link from 'next/link'
+import {
+ parseAsString,
+ parseAsStringLiteral,
+ useQueryState,
+ useQueryStates
+} from 'nuqs'
+
+const paramParser = parseAsString.withDefault('null')
+const components = ['comp1', 'comp2'] as const
+const componentParser = parseAsStringLiteral(components)
+
+const Component = (props: React.ComponentProps<'span'>) => {
+ const [param] = useQueryState('param', paramParser)
+ return {param}
+}
+
+export default function Wolf359() {
+ const [param, setParam] = useQueryState('param', paramParser)
+ const [component, seComponent] = useQueryState('component', componentParser)
+ const [multiple, setMultiple] = useQueryStates({
+ param: paramParser,
+ component: componentParser
+ })
+ return (
+ <>
+
+ {component === 'comp1' && }
+ {component === 'comp2' && }
+
+
+ {param}
+ {component}
+ {multiple.param}
+ {multiple.component}
+
+
+
+
+
+
+
+
+
+ >
+ )
+}
diff --git a/packages/nuqs/src/update-queue.ts b/packages/nuqs/src/update-queue.ts
index 4837775be..7c66d622d 100644
--- a/packages/nuqs/src/update-queue.ts
+++ b/packages/nuqs/src/update-queue.ts
@@ -120,6 +120,7 @@ export function scheduleFlushToURL(router: Router) {
declare global {
interface Window {
next: {
+ version: string
router?: NextRouter & {
state: {
asPath: string
diff --git a/packages/nuqs/src/useQueryState.ts b/packages/nuqs/src/useQueryState.ts
index d9e7989fa..6edfbe3a2 100644
--- a/packages/nuqs/src/useQueryState.ts
+++ b/packages/nuqs/src/useQueryState.ts
@@ -241,6 +241,11 @@ export function useQueryState(
)
React.useEffect(() => {
+ // This will be removed in v2 which will drop support for
+ // partially-functional shallow routing (14.0.2 and 14.0.3)
+ if (window.next.version !== '14.0.3') {
+ return
+ }
const value = initialSearchParams.get(key) ?? null
const state = value === null ? null : safeParse(parse, value, key)
debug('[nuqs `%s`] syncFromUseSearchParams %O', key, state)