Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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'
})
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 @@ -73,9 +73,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 @@ -54,8 +54,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 @@ -55,8 +55,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)
29 changes: 29 additions & 0 deletions packages/e2e/shared/specs/repro-1099.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
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 }))
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
})
17 changes: 1 addition & 16 deletions packages/nuqs/src/adapters/lib/react-router.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
import {
startTransition,
useCallback,
useEffect,
useRef,
useState
} from 'react'
import { startTransition, useCallback, useEffect, useState } from 'react'
import { debug } from '../../lib/debug'
import { createEmitter } from '../../lib/emitter'
import { setQueueResetMutex } from '../../lib/queues/reset'
import { globalThrottleQueue } from '../../lib/queues/throttle'
import { renderQueryString } from '../../lib/url-encoding'
import { createAdapterProvider, type AdapterProvider } from './context'
import type { AdapterInterface, AdapterOptions } from './defs'
Expand Down Expand Up @@ -50,16 +43,9 @@ export function createReactRouterBasedAdapter({
useOptimisticSearchParams: () => URLSearchParams
} {
const emitter = createEmitter<SearchParamsSyncEmitterEvents>()
const enableQueueReset = adapter !== 'react-router-v6'
function useNuqsReactRouterBasedAdapter(
watchKeys: string[]
): AdapterInterface {
const resetRef = useRef(false)
if (enableQueueReset && resetRef.current) {
resetRef.current = false
globalThrottleQueue.reset()
}

const navigate = useNavigate()
const searchParams = useOptimisticSearchParams(watchKeys)
const updateUrl = useCallback(
Expand Down Expand Up @@ -99,7 +85,6 @@ export function createReactRouterBasedAdapter({
if (options.scroll) {
window.scrollTo(0, 0)
}
resetRef.current = enableQueueReset
},
[navigate]
)
Expand Down
Loading
Loading