Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions packages/e2e/next/cypress/e2e/shared/repro-1099.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { testRepro1099 } from 'e2e-shared/specs/repro-1099.cy'

testRepro1099({
path: '/app/repro-1099/useQueryState',
hook: 'useQueryState',
nextJsRouter: 'app'
})

testRepro1099({
path: '/app/repro-1099/useQueryStates',
hook: 'useQueryStates',
nextJsRouter: 'app'
})

testRepro1099({
path: '/pages/repro-1099/useQueryState',
hook: 'useQueryState',
nextJsRouter: 'pages'
})

testRepro1099({
path: '/pages/repro-1099/useQueryStates',
hook: 'useQueryStates',
nextJsRouter: 'pages'
})
1 change: 1 addition & 0 deletions packages/e2e/next/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const basePath =
const config = {
basePath,
productionBrowserSourceMaps: true,
reactStrictMode: false,
experimental: {
clientRouterFilter: false,
...(process.env.REACT_COMPILER === 'true' ? { reactCompiler: true } : {}),
Expand Down
10 changes: 10 additions & 0 deletions packages/e2e/next/src/app/app/repro-1099/useQueryState/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Repro1099UseQueryState } from 'e2e-shared/specs/repro-1099'
import { Suspense } from 'react'

export default function Page() {
return (
<Suspense>
<Repro1099UseQueryState />
</Suspense>
)
}
10 changes: 10 additions & 0 deletions packages/e2e/next/src/app/app/repro-1099/useQueryStates/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Repro1099UseQueryStates } from 'e2e-shared/specs/repro-1099'
import { Suspense } from 'react'

export default function Page() {
return (
<Suspense>
<Repro1099UseQueryStates />
</Suspense>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { Repro1099UseQueryState } from 'e2e-shared/specs/repro-1099'

export default Repro1099UseQueryState
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { Repro1099UseQueryStates } from 'e2e-shared/specs/repro-1099'

export default Repro1099UseQueryStates
11 changes: 11 additions & 0 deletions packages/e2e/react-router/v6/cypress/e2e/shared/repro-1099.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { testRepro1099 } from 'e2e-shared/specs/repro-1099.cy'

testRepro1099({
path: '/repro-1099/useQueryState',
hook: 'useQueryState'
})

testRepro1099({
path: '/repro-1099/useQueryStates',
hook: 'useQueryStates'
})
8 changes: 5 additions & 3 deletions packages/e2e/react-router/v6/src/react-router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,11 @@ const router = createBrowserRouter(
<Route path="render-count/:hook/:shallow/:history/:startTransition/async-loader" lazy={load(import('./routes/render-count.$hook.$shallow.$history.$startTransition.async-loader'))} />

{/* Reproductions */}
<Route path="repro-359" lazy={load(import('./routes/repro-359'))} />
<Route path="repro-839" lazy={load(import('./routes/repro-839'))} />
<Route path="repro-982" lazy={load(import('./routes/repro-982'))} />
<Route path="repro-359" lazy={load(import('./routes/repro-359'))} />
<Route path="repro-839" lazy={load(import('./routes/repro-839'))} />
<Route path="repro-982" lazy={load(import('./routes/repro-982'))} />
<Route path="repro-1099/useQueryState" lazy={load(import('./routes/repro-1099.useQueryState'))} />
<Route path="repro-1099/useQueryStates" lazy={load(import('./routes/repro-1099.useQueryStates'))} />
</Route>
))

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { Repro1099UseQueryState } from 'e2e-shared/specs/repro-1099'

export default Repro1099UseQueryState
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { Repro1099UseQueryStates } from 'e2e-shared/specs/repro-1099'

export default Repro1099UseQueryStates
8 changes: 5 additions & 3 deletions packages/e2e/react-router/v7/app/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,10 @@ export default [
route('/render-count/:hook/:shallow/:history/:startTransition/async-loader', './routes/render-count.$hook.$shallow.$history.$startTransition.async-loader.tsx'),

// Reproductions
route('/repro-359', './routes/repro-359.tsx'),
route('/repro-839', './routes/repro-839.tsx'),
route('/repro-982', './routes/repro-982.tsx'),
route('/repro-359', './routes/repro-359.tsx'),
route('/repro-839', './routes/repro-839.tsx'),
route('/repro-982', './routes/repro-982.tsx'),
route('/repro-1099/useQueryState', './routes/repro-1099.useQueryState.tsx'),
route('/repro-1099/useQueryStates', './routes/repro-1099.useQueryStates.tsx'),
])
] satisfies RouteConfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { Repro1099UseQueryState } from 'e2e-shared/specs/repro-1099'

export default Repro1099UseQueryState
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { Repro1099UseQueryStates } from 'e2e-shared/specs/repro-1099'

export default Repro1099UseQueryStates
11 changes: 11 additions & 0 deletions packages/e2e/react-router/v7/cypress/e2e/shared/repro-1099.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { testRepro1099 } from 'e2e-shared/specs/repro-1099.cy'

testRepro1099({
path: '/repro-1099/useQueryState',
hook: 'useQueryState'
})

testRepro1099({
path: '/repro-1099/useQueryStates',
hook: 'useQueryStates'
})
13 changes: 13 additions & 0 deletions packages/e2e/react/cypress/e2e/shared/repro-1099.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { testRepro1099 } from 'e2e-shared/specs/repro-1099.cy'

testRepro1099({
path: '/repro-1099/useQueryState',
hook: 'useQueryState',
shallowOptions: [true]
})

testRepro1099({
path: '/repro-1099/useQueryStates',
hook: 'useQueryStates',
shallowOptions: [true]
})
6 changes: 4 additions & 2 deletions packages/e2e/react/src/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,10 @@ const routes: Record<string, React.LazyExoticComponent<() => JSX.Element>> = {
'/render-count/useQueryStates/false/push/true': lazy(() => import('./routes/render-count')),

// Reproductions
'/repro-359': lazy(() => import('./routes/repro-359')),
'/repro-982': lazy(() => import('./routes/repro-982')),
'/repro-359': lazy(() => import('./routes/repro-359')),
'/repro-982': lazy(() => import('./routes/repro-982')),
'/repro-1099/useQueryState': lazy(() => import('./routes/repro-1099.useQueryState')),
'/repro-1099/useQueryStates': lazy(() => import('./routes/repro-1099.useQueryStates')),
}

export function Router() {
Expand Down
3 changes: 3 additions & 0 deletions packages/e2e/react/src/routes/repro-1099.useQueryState.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { Repro1099UseQueryState } from 'e2e-shared/specs/repro-1099'

export default Repro1099UseQueryState
3 changes: 3 additions & 0 deletions packages/e2e/react/src/routes/repro-1099.useQueryStates.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { Repro1099UseQueryStates } from 'e2e-shared/specs/repro-1099'

export default Repro1099UseQueryStates
3 changes: 3 additions & 0 deletions packages/e2e/remix/app/routes/repro-1099.useQueryState.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { Repro1099UseQueryState } from 'e2e-shared/specs/repro-1099'

export default Repro1099UseQueryState
3 changes: 3 additions & 0 deletions packages/e2e/remix/app/routes/repro-1099.useQueryStates.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { Repro1099UseQueryStates } from 'e2e-shared/specs/repro-1099'

export default Repro1099UseQueryStates
11 changes: 11 additions & 0 deletions packages/e2e/remix/cypress/e2e/shared/repro-1099.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { testRepro1099 } from 'e2e-shared/specs/repro-1099.cy'

testRepro1099({
path: '/repro-1099/useQueryState',
hook: 'useQueryState'
})

testRepro1099({
path: '/repro-1099/useQueryStates',
hook: 'useQueryStates'
})
12 changes: 8 additions & 4 deletions packages/e2e/shared/components/null-detector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,27 @@ type NullDetectorProps = {
state: ReactNode
id?: string
throwOnNull?: boolean
enabled?: boolean
}

export function NullDetector({
state,
id = 'null-detector',
throwOnNull = false
throwOnNull = false,
enabled = true
}: NullDetectorProps) {
const [hasBeenNullAtSomePoint, set] = useState(() => state === null)
const [hasBeenNullAtSomePoint, set] = useState(() =>
enabled ? state === null : false
)
useEffect(() => {
if (state !== null) {
if (state !== null || !enabled) {
return
}
if (throwOnNull) {
throw new Error(`Null detected in <NullDetector id="${id}">`)
}
console.error(`<NullDetector id="${id}">: NULL DETECTED`)
set(true)
}, [state])
}, [enabled, state])
return <pre id={id}>{hasBeenNullAtSomePoint ? 'fail' : 'pass'}</pre>
}
5 changes: 3 additions & 2 deletions packages/e2e/shared/lib/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ import {
createLoader,
createSerializer,
parseAsBoolean,
parseAsStringLiteral
parseAsStringLiteral,
useQueryStates
} from 'nuqs'

export const optionsSearchParams = {
shallow: parseAsBoolean.withDefault(true),
history: parseAsStringLiteral(['replace', 'push']).withDefault('replace')
}

export const useOptions = () => useQueryStates(optionsSearchParams)[0]
export const getOptionsUrl = createSerializer(optionsSearchParams)
export const loadOptions = createLoader(optionsSearchParams)
8 changes: 2 additions & 6 deletions packages/e2e/shared/specs/life-and-death.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ function ChildComponentUseQueryState() {
Clear
</button>
<Display environment="client" target="useQueryState" state={test} />
<NullDetector throwOnNull id="null-detector-useQueryState" state={test} />
<NullDetector id="null-detector-useQueryState" state={test} />
</>
)
}
Expand All @@ -63,11 +63,7 @@ function ChildComponentUseQueryStates() {
Clear
</button>
<Display environment="client" target="useQueryStates" state={test} />
<NullDetector
throwOnNull
id="null-detector-useQueryStates"
state={test}
/>
<NullDetector id="null-detector-useQueryStates" state={test} />
</>
)
}
33 changes: 33 additions & 0 deletions packages/e2e/shared/specs/repro-1099.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { createTest, type TestConfig } from '../create-test'
import { getOptionsUrl } from '../lib/options'

type TestRepro1099Options = TestConfig & {
shallowOptions?: boolean[]
historyOptions?: ('replace' | 'push')[]
}

export function testRepro1099({
shallowOptions = [true, false],
historyOptions = ['replace', 'push'],
...options
}: TestRepro1099Options) {
const factory = createTest('repro-1099', ({ path }) => {
for (const shallow of shallowOptions) {
for (const history of historyOptions) {
it(`should not emit null during updates with { shallow: ${shallow}, history: '${history}' }`, () => {
cy.visit(getOptionsUrl(path, { history, shallow }), {
onBeforeLoad(win) {
win.localStorage.setItem('debug', 'nuqs')
}
})
cy.contains('#hydration-marker', 'hydrated').should('be.hidden')
cy.get('#null-detector').should('have.text', 'pass')
cy.get('button').click()
cy.location('search').should('match', /test=pass$/)
cy.get('#null-detector').should('have.text', 'pass')
})
}
}
})
return factory(options)
}
68 changes: 68 additions & 0 deletions packages/e2e/shared/specs/repro-1099.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
'use client'

import { parseAsString, useQueryState, useQueryStates } from 'nuqs'
import { useEffect, useState } from 'react'
import { NullDetector } from '../components/null-detector'
import { useOptions } from '../lib/options'

export function Repro1099UseQueryState() {
const { shallow, history } = useOptions()
const [state, setState] = useQueryState('test', { shallow, history })
const [isNullDetectorEnabled, setIsNullDetectorEnabled] = useState(false)
useStateUpdateInEffect(state)
return (
<>
<button
onClick={() => {
setIsNullDetectorEnabled(true)
setState('pass')
}}
>
Test
</button>
<NullDetector state={state} enabled={isNullDetectorEnabled} />
</>
)
}

export function Repro1099UseQueryStates() {
const { shallow, history } = useOptions()
const [{ state }, setSearchParams] = useQueryStates(
{
state: parseAsString
},
{
shallow,
history,
urlKeys: {
state: 'test'
}
}
)
const [isNullDetectorEnabled, setIsNullDetectorEnabled] = useState(false)
useStateUpdateInEffect(state)
return (
<>
<button
onClick={() => {
setIsNullDetectorEnabled(true)
setSearchParams({ state: 'pass' })
}}
>
Test
</button>
<NullDetector state={state} enabled={isNullDetectorEnabled} />
</>
)
}

function useStateUpdateInEffect(trigger: unknown) {
const [, setCount] = useState(0)
useEffect(() => {
if (!trigger) {
return
}
console.log('[repro-1099] trigger other state update')
setCount(x => x + 1)
}, [trigger])
}
11 changes: 11 additions & 0 deletions packages/e2e/tanstack-router/cypress/e2e/shared/repro-1099.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { testRepro1099 } from 'e2e-shared/specs/repro-1099.cy'

testRepro1099({
path: '/repro-1099/useQueryState',
hook: 'useQueryState'
})

testRepro1099({
path: '/repro-1099/useQueryStates',
hook: 'useQueryStates'
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { createFileRoute } from '@tanstack/react-router'
import { Repro1099UseQueryState } from 'e2e-shared/specs/repro-1099'

export const Route = createFileRoute('/repro-1099/useQueryState')({
component: Repro1099UseQueryState
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { createFileRoute } from '@tanstack/react-router'
import { Repro1099UseQueryStates } from 'e2e-shared/specs/repro-1099'

export const Route = createFileRoute('/repro-1099/useQueryStates')({
component: Repro1099UseQueryStates
})
5 changes: 4 additions & 1 deletion packages/nuqs/src/adapters/lib/react-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,10 @@ export function createReactRouterBasedAdapter({
const searchParams = useOptimisticSearchParams(watchKeys)
const updateUrl = useCallback(
(search: URLSearchParams, options: AdapterOptions) => {
startTransition(() => {
const maybeTransition = options.shallow
? (callback: () => void) => callback()
: startTransition
maybeTransition(() => {
emitter.emit('update', search)
})
const url = new URL(location.href)
Expand Down
Loading
Loading