From 6f84ce06e4fbf24ff5a2e68da10f7676095f73e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Best?= Date: Thu, 30 Jan 2025 10:23:50 +0100 Subject: [PATCH 1/3] test: Add conditional rendering test Should fail on all react-router based frameworks. --- .../e2e/shared/conditional-rendering.cy.ts | 25 ++++++++ .../useQueryState/page.tsx | 10 ++++ .../useQueryStates/page.tsx | 10 ++++ .../conditional-rendering/useQueryState.tsx | 3 + .../conditional-rendering/useQueryStates.tsx | 3 + .../e2e/shared/conditional-rendering.cy.ts | 11 ++++ .../e2e/react-router/v6/src/react-router.tsx | 2 + .../conditional-rendering.useQueryState.tsx | 3 + .../conditional-rendering.useQueryStates.tsx | 3 + packages/e2e/react-router/v7/app/routes.ts | 2 + .../conditional-rendering.useQueryState.tsx | 3 + .../conditional-rendering.useQueryStates.tsx | 3 + .../e2e/shared/conditional-rendering.cy.ts | 11 ++++ .../e2e/shared/conditional-rendering.cy.ts | 11 ++++ packages/e2e/react/src/routes.tsx | 2 + .../conditional-rendering.useQueryState.tsx | 3 + .../conditional-rendering.useQueryStates.tsx | 3 + .../conditional-rendering.useQueryState.tsx | 3 + .../conditional-rendering.useQueryStates.tsx | 3 + .../e2e/shared/conditional-rendering.cy.ts | 11 ++++ .../shared/specs/conditional-rendering.cy.ts | 36 +++++++++++ .../shared/specs/conditional-rendering.tsx | 60 +++++++++++++++++++ 22 files changed, 221 insertions(+) create mode 100644 packages/e2e/next/cypress/e2e/shared/conditional-rendering.cy.ts create mode 100644 packages/e2e/next/src/app/app/(shared)/conditional-rendering/useQueryState/page.tsx create mode 100644 packages/e2e/next/src/app/app/(shared)/conditional-rendering/useQueryStates/page.tsx create mode 100644 packages/e2e/next/src/pages/pages/conditional-rendering/useQueryState.tsx create mode 100644 packages/e2e/next/src/pages/pages/conditional-rendering/useQueryStates.tsx create mode 100644 packages/e2e/react-router/v6/cypress/e2e/shared/conditional-rendering.cy.ts create mode 100644 packages/e2e/react-router/v6/src/routes/conditional-rendering.useQueryState.tsx create mode 100644 packages/e2e/react-router/v6/src/routes/conditional-rendering.useQueryStates.tsx create mode 100644 packages/e2e/react-router/v7/app/routes/conditional-rendering.useQueryState.tsx create mode 100644 packages/e2e/react-router/v7/app/routes/conditional-rendering.useQueryStates.tsx create mode 100644 packages/e2e/react-router/v7/cypress/e2e/shared/conditional-rendering.cy.ts create mode 100644 packages/e2e/react/cypress/e2e/shared/conditional-rendering.cy.ts create mode 100644 packages/e2e/react/src/routes/conditional-rendering.useQueryState.tsx create mode 100644 packages/e2e/react/src/routes/conditional-rendering.useQueryStates.tsx create mode 100644 packages/e2e/remix/app/routes/conditional-rendering.useQueryState.tsx create mode 100644 packages/e2e/remix/app/routes/conditional-rendering.useQueryStates.tsx create mode 100644 packages/e2e/remix/cypress/e2e/shared/conditional-rendering.cy.ts create mode 100644 packages/e2e/shared/specs/conditional-rendering.cy.ts create mode 100644 packages/e2e/shared/specs/conditional-rendering.tsx diff --git a/packages/e2e/next/cypress/e2e/shared/conditional-rendering.cy.ts b/packages/e2e/next/cypress/e2e/shared/conditional-rendering.cy.ts new file mode 100644 index 000000000..07952ea1f --- /dev/null +++ b/packages/e2e/next/cypress/e2e/shared/conditional-rendering.cy.ts @@ -0,0 +1,25 @@ +import { testConditionalRendering } from 'e2e-shared/specs/conditional-rendering.cy' + +testConditionalRendering({ + path: '/app/conditional-rendering/useQueryState', + hook: 'useQueryState', + nextJsRouter: 'app' +}) + +testConditionalRendering({ + path: '/app/conditional-rendering/useQueryStates', + hook: 'useQueryStates', + nextJsRouter: 'app' +}) + +testConditionalRendering({ + path: '/pages/conditional-rendering/useQueryState', + hook: 'useQueryState', + nextJsRouter: 'pages' +}) + +testConditionalRendering({ + path: '/pages/conditional-rendering/useQueryStates', + hook: 'useQueryStates', + nextJsRouter: 'pages' +}) diff --git a/packages/e2e/next/src/app/app/(shared)/conditional-rendering/useQueryState/page.tsx b/packages/e2e/next/src/app/app/(shared)/conditional-rendering/useQueryState/page.tsx new file mode 100644 index 000000000..ef9ff2ed0 --- /dev/null +++ b/packages/e2e/next/src/app/app/(shared)/conditional-rendering/useQueryState/page.tsx @@ -0,0 +1,10 @@ +import { ConditionalRenderingUseQueryState } from 'e2e-shared/specs/conditional-rendering' +import { Suspense } from 'react' + +export default function Page() { + return ( + + + + ) +} diff --git a/packages/e2e/next/src/app/app/(shared)/conditional-rendering/useQueryStates/page.tsx b/packages/e2e/next/src/app/app/(shared)/conditional-rendering/useQueryStates/page.tsx new file mode 100644 index 000000000..923430312 --- /dev/null +++ b/packages/e2e/next/src/app/app/(shared)/conditional-rendering/useQueryStates/page.tsx @@ -0,0 +1,10 @@ +import { ConditionalRenderingUseQueryStates } from 'e2e-shared/specs/conditional-rendering' +import { Suspense } from 'react' + +export default function Page() { + return ( + + + + ) +} diff --git a/packages/e2e/next/src/pages/pages/conditional-rendering/useQueryState.tsx b/packages/e2e/next/src/pages/pages/conditional-rendering/useQueryState.tsx new file mode 100644 index 000000000..42089b432 --- /dev/null +++ b/packages/e2e/next/src/pages/pages/conditional-rendering/useQueryState.tsx @@ -0,0 +1,3 @@ +import { ConditionalRenderingUseQueryState } from 'e2e-shared/specs/conditional-rendering' + +export default ConditionalRenderingUseQueryState diff --git a/packages/e2e/next/src/pages/pages/conditional-rendering/useQueryStates.tsx b/packages/e2e/next/src/pages/pages/conditional-rendering/useQueryStates.tsx new file mode 100644 index 000000000..8715144e1 --- /dev/null +++ b/packages/e2e/next/src/pages/pages/conditional-rendering/useQueryStates.tsx @@ -0,0 +1,3 @@ +import { ConditionalRenderingUseQueryStates } from 'e2e-shared/specs/conditional-rendering' + +export default ConditionalRenderingUseQueryStates diff --git a/packages/e2e/react-router/v6/cypress/e2e/shared/conditional-rendering.cy.ts b/packages/e2e/react-router/v6/cypress/e2e/shared/conditional-rendering.cy.ts new file mode 100644 index 000000000..333517391 --- /dev/null +++ b/packages/e2e/react-router/v6/cypress/e2e/shared/conditional-rendering.cy.ts @@ -0,0 +1,11 @@ +import { testConditionalRendering } from 'e2e-shared/specs/conditional-rendering.cy' + +testConditionalRendering({ + path: '/conditional-rendering/useQueryState', + hook: 'useQueryState' +}) + +testConditionalRendering({ + path: '/conditional-rendering/useQueryStates', + hook: 'useQueryStates' +}) diff --git a/packages/e2e/react-router/v6/src/react-router.tsx b/packages/e2e/react-router/v6/src/react-router.tsx index 702acce03..5cffb4d09 100644 --- a/packages/e2e/react-router/v6/src/react-router.tsx +++ b/packages/e2e/react-router/v6/src/react-router.tsx @@ -41,6 +41,8 @@ const router = createBrowserRouter( + + diff --git a/packages/e2e/react-router/v6/src/routes/conditional-rendering.useQueryState.tsx b/packages/e2e/react-router/v6/src/routes/conditional-rendering.useQueryState.tsx new file mode 100644 index 000000000..42089b432 --- /dev/null +++ b/packages/e2e/react-router/v6/src/routes/conditional-rendering.useQueryState.tsx @@ -0,0 +1,3 @@ +import { ConditionalRenderingUseQueryState } from 'e2e-shared/specs/conditional-rendering' + +export default ConditionalRenderingUseQueryState diff --git a/packages/e2e/react-router/v6/src/routes/conditional-rendering.useQueryStates.tsx b/packages/e2e/react-router/v6/src/routes/conditional-rendering.useQueryStates.tsx new file mode 100644 index 000000000..8715144e1 --- /dev/null +++ b/packages/e2e/react-router/v6/src/routes/conditional-rendering.useQueryStates.tsx @@ -0,0 +1,3 @@ +import { ConditionalRenderingUseQueryStates } from 'e2e-shared/specs/conditional-rendering' + +export default ConditionalRenderingUseQueryStates diff --git a/packages/e2e/react-router/v7/app/routes.ts b/packages/e2e/react-router/v7/app/routes.ts index d55ccfbc0..29751d350 100644 --- a/packages/e2e/react-router/v7/app/routes.ts +++ b/packages/e2e/react-router/v7/app/routes.ts @@ -24,6 +24,8 @@ export default [ route('/form/useQueryStates', './routes/form.useQueryStates.tsx'), route('/referential-stability/useQueryState', './routes/referential-stability.useQueryState.tsx'), route('/referential-stability/useQueryStates', './routes/referential-stability.useQueryStates.tsx'), + route('/conditional-rendering/useQueryState', './routes/conditional-rendering.useQueryState.tsx'), + route('/conditional-rendering/useQueryStates', './routes/conditional-rendering.useQueryStates.tsx'), route('/render-count/:hook/:shallow/:history/:startTransition/no-loader', './routes/render-count.$hook.$shallow.$history.$startTransition.no-loader.tsx'), route('/render-count/:hook/:shallow/:history/:startTransition/sync-loader', './routes/render-count.$hook.$shallow.$history.$startTransition.sync-loader.tsx'), route('/render-count/:hook/:shallow/:history/:startTransition/async-loader', './routes/render-count.$hook.$shallow.$history.$startTransition.async-loader.tsx'), diff --git a/packages/e2e/react-router/v7/app/routes/conditional-rendering.useQueryState.tsx b/packages/e2e/react-router/v7/app/routes/conditional-rendering.useQueryState.tsx new file mode 100644 index 000000000..42089b432 --- /dev/null +++ b/packages/e2e/react-router/v7/app/routes/conditional-rendering.useQueryState.tsx @@ -0,0 +1,3 @@ +import { ConditionalRenderingUseQueryState } from 'e2e-shared/specs/conditional-rendering' + +export default ConditionalRenderingUseQueryState diff --git a/packages/e2e/react-router/v7/app/routes/conditional-rendering.useQueryStates.tsx b/packages/e2e/react-router/v7/app/routes/conditional-rendering.useQueryStates.tsx new file mode 100644 index 000000000..8715144e1 --- /dev/null +++ b/packages/e2e/react-router/v7/app/routes/conditional-rendering.useQueryStates.tsx @@ -0,0 +1,3 @@ +import { ConditionalRenderingUseQueryStates } from 'e2e-shared/specs/conditional-rendering' + +export default ConditionalRenderingUseQueryStates diff --git a/packages/e2e/react-router/v7/cypress/e2e/shared/conditional-rendering.cy.ts b/packages/e2e/react-router/v7/cypress/e2e/shared/conditional-rendering.cy.ts new file mode 100644 index 000000000..333517391 --- /dev/null +++ b/packages/e2e/react-router/v7/cypress/e2e/shared/conditional-rendering.cy.ts @@ -0,0 +1,11 @@ +import { testConditionalRendering } from 'e2e-shared/specs/conditional-rendering.cy' + +testConditionalRendering({ + path: '/conditional-rendering/useQueryState', + hook: 'useQueryState' +}) + +testConditionalRendering({ + path: '/conditional-rendering/useQueryStates', + hook: 'useQueryStates' +}) diff --git a/packages/e2e/react/cypress/e2e/shared/conditional-rendering.cy.ts b/packages/e2e/react/cypress/e2e/shared/conditional-rendering.cy.ts new file mode 100644 index 000000000..333517391 --- /dev/null +++ b/packages/e2e/react/cypress/e2e/shared/conditional-rendering.cy.ts @@ -0,0 +1,11 @@ +import { testConditionalRendering } from 'e2e-shared/specs/conditional-rendering.cy' + +testConditionalRendering({ + path: '/conditional-rendering/useQueryState', + hook: 'useQueryState' +}) + +testConditionalRendering({ + path: '/conditional-rendering/useQueryStates', + hook: 'useQueryStates' +}) diff --git a/packages/e2e/react/src/routes.tsx b/packages/e2e/react/src/routes.tsx index 3cd6f61fb..953fdb5a1 100644 --- a/packages/e2e/react/src/routes.tsx +++ b/packages/e2e/react/src/routes.tsx @@ -21,6 +21,8 @@ const routes: Record JSX.Element>> = { '/form/useQueryStates': lazy(() => import('./routes/form.useQueryStates')), '/referential-stability/useQueryState': lazy(() => import('./routes/referential-stability.useQueryState')), '/referential-stability/useQueryStates': lazy(() => import('./routes/referential-stability.useQueryStates')), + '/conditional-rendering/useQueryState': lazy(() => import('./routes/conditional-rendering.useQueryState')), + '/conditional-rendering/useQueryStates': lazy(() => import('./routes/conditional-rendering.useQueryStates')), '/render-count/useQueryState/true/replace/false': lazy(() => import('./routes/render-count')), '/render-count/useQueryState/true/replace/true': lazy(() => import('./routes/render-count')), diff --git a/packages/e2e/react/src/routes/conditional-rendering.useQueryState.tsx b/packages/e2e/react/src/routes/conditional-rendering.useQueryState.tsx new file mode 100644 index 000000000..42089b432 --- /dev/null +++ b/packages/e2e/react/src/routes/conditional-rendering.useQueryState.tsx @@ -0,0 +1,3 @@ +import { ConditionalRenderingUseQueryState } from 'e2e-shared/specs/conditional-rendering' + +export default ConditionalRenderingUseQueryState diff --git a/packages/e2e/react/src/routes/conditional-rendering.useQueryStates.tsx b/packages/e2e/react/src/routes/conditional-rendering.useQueryStates.tsx new file mode 100644 index 000000000..8715144e1 --- /dev/null +++ b/packages/e2e/react/src/routes/conditional-rendering.useQueryStates.tsx @@ -0,0 +1,3 @@ +import { ConditionalRenderingUseQueryStates } from 'e2e-shared/specs/conditional-rendering' + +export default ConditionalRenderingUseQueryStates diff --git a/packages/e2e/remix/app/routes/conditional-rendering.useQueryState.tsx b/packages/e2e/remix/app/routes/conditional-rendering.useQueryState.tsx new file mode 100644 index 000000000..42089b432 --- /dev/null +++ b/packages/e2e/remix/app/routes/conditional-rendering.useQueryState.tsx @@ -0,0 +1,3 @@ +import { ConditionalRenderingUseQueryState } from 'e2e-shared/specs/conditional-rendering' + +export default ConditionalRenderingUseQueryState diff --git a/packages/e2e/remix/app/routes/conditional-rendering.useQueryStates.tsx b/packages/e2e/remix/app/routes/conditional-rendering.useQueryStates.tsx new file mode 100644 index 000000000..8715144e1 --- /dev/null +++ b/packages/e2e/remix/app/routes/conditional-rendering.useQueryStates.tsx @@ -0,0 +1,3 @@ +import { ConditionalRenderingUseQueryStates } from 'e2e-shared/specs/conditional-rendering' + +export default ConditionalRenderingUseQueryStates diff --git a/packages/e2e/remix/cypress/e2e/shared/conditional-rendering.cy.ts b/packages/e2e/remix/cypress/e2e/shared/conditional-rendering.cy.ts new file mode 100644 index 000000000..333517391 --- /dev/null +++ b/packages/e2e/remix/cypress/e2e/shared/conditional-rendering.cy.ts @@ -0,0 +1,11 @@ +import { testConditionalRendering } from 'e2e-shared/specs/conditional-rendering.cy' + +testConditionalRendering({ + path: '/conditional-rendering/useQueryState', + hook: 'useQueryState' +}) + +testConditionalRendering({ + path: '/conditional-rendering/useQueryStates', + hook: 'useQueryStates' +}) diff --git a/packages/e2e/shared/specs/conditional-rendering.cy.ts b/packages/e2e/shared/specs/conditional-rendering.cy.ts new file mode 100644 index 000000000..a4200e98e --- /dev/null +++ b/packages/e2e/shared/specs/conditional-rendering.cy.ts @@ -0,0 +1,36 @@ +import { createTest } from '../create-test' + +export const testConditionalRendering = createTest( + 'Conditional rendering', + ({ path }) => { + it('should have the correct initial state after mounting', () => { + cy.visit(path + '?test=pass') + cy.contains('#hydration-marker', 'hydrated').should('be.hidden') + cy.get('button#mount').click() + cy.get('#state').should('have.text', 'pass') + }) + it('should keep the correct state after unmounting and remounting', () => { + cy.visit(path) + cy.contains('#hydration-marker', 'hydrated').should('be.hidden') + cy.get('button#mount').click() + cy.get('button#set').click() + cy.get('button#unmount').click() + cy.get('button#mount').click() + cy.get('#state').should('have.text', 'pass') + }) + // it('should pick up state changes that occur in the same tick as the mount', () => { + // cy.visit(path) + // cy.contains('#hydration-marker', 'hydrated').should('be.hidden') + // cy.get('button#mount-and-set').click() + // cy.get('#state').should('have.text', 'pass') + // }) + // it('should pick up state changes that occur in the same tick as the unmount when remounting', () => { + // cy.visit(path) + // cy.contains('#hydration-marker', 'hydrated').should('be.hidden') + // cy.get('button#mount').click() + // cy.get('button#unmount-and-set').click() + // cy.get('button#mount').click() + // cy.get('#state').should('have.text', 'pass') + // }) + } +) diff --git a/packages/e2e/shared/specs/conditional-rendering.tsx b/packages/e2e/shared/specs/conditional-rendering.tsx new file mode 100644 index 000000000..9c6e3ce53 --- /dev/null +++ b/packages/e2e/shared/specs/conditional-rendering.tsx @@ -0,0 +1,60 @@ +'use client' + +import { parseAsString, useQueryState, useQueryStates } from 'nuqs' +import { type FC, useState } from 'react' + +function ConditionalRenderer({ Component }: { Component: FC }) { + const [mounted, setMounted] = useState(false) + return ( + <> + + + {mounted && } + + ) +} + +// -- + +function TestComponentUseQueryState() { + const [state, setState] = useQueryState('test') + return ( + <> + +
{state}
+ + ) +} + +function TestComponentUseQueryStates() { + const [{ state }, setState] = useQueryStates( + { + state: parseAsString + }, + { urlKeys: { state: 'test' } } + ) + return ( + <> + +
{state}
+ + ) +} + +// -- + +export function ConditionalRenderingUseQueryState() { + return +} + +export function ConditionalRenderingUseQueryStates() { + return +} From ffa8d80d5ccff1a4b1945b4544ea44d177ced5e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Best?= Date: Thu, 30 Jan 2025 10:29:23 +0100 Subject: [PATCH 2/3] fix: Read initial search params on mount in React Router useSearchParams For some reason the initial values aren't returned from useSearchParams when mounting components after initial page load, unless you fill those up as the `defaultInit` argument. --- packages/nuqs/src/adapters/lib/react-router.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/nuqs/src/adapters/lib/react-router.ts b/packages/nuqs/src/adapters/lib/react-router.ts index 408b2df60..828db7e4f 100644 --- a/packages/nuqs/src/adapters/lib/react-router.ts +++ b/packages/nuqs/src/adapters/lib/react-router.ts @@ -20,8 +20,7 @@ type NavigateOptions = { } type NavigateFn = (url: NavigateUrl, options: NavigateOptions) => void type UseNavigate = () => NavigateFn - -type UseSearchParams = () => [URLSearchParams, {}] +type UseSearchParams = (initial: URLSearchParams) => [URLSearchParams, {}] // -- @@ -78,7 +77,11 @@ export function createReactRouterBasedAdapter( } } function useOptimisticSearchParams() { - const [serverSearchParams] = useSearchParams() + const [serverSearchParams] = useSearchParams( + typeof location === 'undefined' + ? new URLSearchParams() + : new URLSearchParams(location.search) + ) const [searchParams, setSearchParams] = useState(serverSearchParams) useEffect(() => { function onPopState() { From 4d50df61dc9070f3ff25ecfaf21313590adfe13d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Best?= Date: Thu, 30 Jan 2025 13:23:51 +0100 Subject: [PATCH 3/3] chore: Remove test fixture --- .../e2e/shared/specs/conditional-rendering.cy.ts | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/packages/e2e/shared/specs/conditional-rendering.cy.ts b/packages/e2e/shared/specs/conditional-rendering.cy.ts index a4200e98e..148ee80e6 100644 --- a/packages/e2e/shared/specs/conditional-rendering.cy.ts +++ b/packages/e2e/shared/specs/conditional-rendering.cy.ts @@ -18,19 +18,5 @@ export const testConditionalRendering = createTest( cy.get('button#mount').click() cy.get('#state').should('have.text', 'pass') }) - // it('should pick up state changes that occur in the same tick as the mount', () => { - // cy.visit(path) - // cy.contains('#hydration-marker', 'hydrated').should('be.hidden') - // cy.get('button#mount-and-set').click() - // cy.get('#state').should('have.text', 'pass') - // }) - // it('should pick up state changes that occur in the same tick as the unmount when remounting', () => { - // cy.visit(path) - // cy.contains('#hydration-marker', 'hydrated').should('be.hidden') - // cy.get('button#mount').click() - // cy.get('button#unmount-and-set').click() - // cy.get('button#mount').click() - // cy.get('#state').should('have.text', 'pass') - // }) } )