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..148ee80e6 --- /dev/null +++ b/packages/e2e/shared/specs/conditional-rendering.cy.ts @@ -0,0 +1,22 @@ +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') + }) + } +) 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 +} 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() {