Skip to content

Commit 1f7afe7

Browse files
committed
feat: Expose shorthand helpers for limitUrlUpdates
1 parent 590dda5 commit 1f7afe7

File tree

9 files changed

+65
-55
lines changed

9 files changed

+65
-55
lines changed

packages/e2e/next/src/app/app/debounce/client.tsx

+19-22
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
'use client'
22

3-
import { parseAsInteger, useQueryState, useQueryStates } from 'nuqs'
3+
import {
4+
debounce,
5+
parseAsInteger,
6+
throttle,
7+
useQueryState,
8+
useQueryStates
9+
} from 'nuqs'
410
import { searchParams, urlKeys } from './search-params'
511

612
export function Client() {
713
const [timeMs, setTimeMs] = useQueryState(
814
'debounceTime',
915
parseAsInteger.withDefault(100).withOptions({
10-
limitUrlUpdates: {
11-
method: 'throttle',
12-
timeMs: 200
13-
}
16+
// No real need to throttle this one, but it showcases usage:
17+
limitUrlUpdates: throttle(200)
1418
})
1519
)
1620
const [{ search, pageIndex }, setSearchParams] = useQueryStates(
@@ -28,30 +32,23 @@ export function Client() {
2832
setSearchParams(
2933
{ search: e.target.value },
3034
{
31-
limitUrlUpdates: {
32-
method: e.target.value === '' ? 'throttle' : 'debounce',
33-
timeMs: e.target.value === '' ? 50 : timeMs
34-
}
35+
// Instant update when clearing the input, otherwise debounce
36+
limitUrlUpdates:
37+
e.target.value === '' ? undefined : debounce(timeMs)
3538
}
3639
)
3740
}
41+
onKeyDown={e => {
42+
if (e.key === 'Enter') {
43+
// Send the search immediately when pressing Enter
44+
setSearchParams({ search: e.currentTarget.value })
45+
}
46+
}}
3847
/>
3948
<button onClick={() => setSearchParams({ pageIndex: pageIndex + 1 })}>
4049
Next Page
4150
</button>
42-
<button
43-
onClick={() => {
44-
setTimeMs(null)
45-
setSearchParams(null, {
46-
limitUrlUpdates: {
47-
method: 'throttle',
48-
timeMs: 50
49-
}
50-
})
51-
}}
52-
>
53-
Reset
54-
</button>
51+
<button onClick={() => setSearchParams(null)}>Reset</button>
5552
<div style={{ marginTop: '1rem' }}>
5653
<label style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
5754
<input

packages/e2e/next/src/app/app/debounce/search-params.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@ import {
66
} from 'nuqs/server'
77

88
export const searchParams = {
9-
search: parseAsString.withDefault('').withOptions({
10-
limitUrlUpdates: { method: 'debounce', timeMs: 2000 }
11-
}),
9+
search: parseAsString.withDefault(''),
1210
pageIndex: parseAsInteger.withDefault(0)
1311
}
1412
export const urlKeys: UrlKeys<typeof searchParams> = {

packages/nuqs/src/defs.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import type { TransitionStartFunction } from 'react'
22

33
export type SearchParams = Record<string, string | string[] | undefined>
44
export type HistoryOptions = 'replace' | 'push'
5+
export type LimitUrlUpdates =
6+
| { method: 'debounce'; timeMs: number }
7+
| { method: 'throttle'; timeMs: number }
58

69
export type Options = {
710
/**
@@ -55,10 +58,7 @@ export type Options = {
5558
* If both `throttleMs` and `limitUrlUpdates` are set, `limitUrlUpdates` will
5659
* take precedence.
5760
*/
58-
limitUrlUpdates?: {
59-
method: 'debounce' | 'throttle'
60-
timeMs: number
61-
}
61+
limitUrlUpdates?: LimitUrlUpdates
6262

6363
/**
6464
* In RSC frameworks, opt-in to observing Server Component loading states when

packages/nuqs/src/index.server.ts

+5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ export type {
66
SearchParams,
77
UrlKeys
88
} from './defs'
9+
export {
10+
debounce,
11+
defaultRateLimit,
12+
throttle
13+
} from './lib/queues/rate-limiting'
914
export {
1015
createLoader,
1116
type LoaderFunction,

packages/nuqs/src/index.ts

+5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ export type {
55
SearchParams,
66
UrlKeys
77
} from './defs'
8+
export {
9+
debounce,
10+
defaultRateLimit,
11+
throttle
12+
} from './lib/queues/rate-limiting'
813
export {
914
createLoader,
1015
type LoaderFunction,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import type { LimitUrlUpdates } from '../../defs'
2+
3+
// edit: Safari 17 now allows 100 calls per 10 seconds, a bit better.
4+
function getDefaultThrottle() {
5+
if (typeof window === 'undefined') return 50
6+
// https://stackoverflow.com/questions/7944460/detect-safari-browser
7+
// @ts-expect-error
8+
const isSafari = Boolean(window.GestureEvent)
9+
if (!isSafari) {
10+
return 50
11+
}
12+
try {
13+
const match = navigator.userAgent?.match(/version\/([\d\.]+) safari/i)
14+
return parseFloat(match![1]!) >= 17 ? 120 : 320
15+
} catch {
16+
return 320
17+
}
18+
}
19+
20+
export function throttle(timeMs: number): LimitUrlUpdates {
21+
return { method: 'throttle', timeMs }
22+
}
23+
24+
export function debounce(timeMs: number): LimitUrlUpdates {
25+
return { method: 'debounce', timeMs }
26+
}
27+
28+
export const defaultRateLimit = throttle(getDefaultThrottle())

packages/nuqs/src/lib/queues/throttle.ts

+1-24
Original file line numberDiff line numberDiff line change
@@ -7,30 +7,7 @@ import { compose } from '../compose'
77
import { debug } from '../debug'
88
import { error } from '../errors'
99
import { withResolvers, type Resolvers } from '../with-resolvers'
10-
11-
// edit: Safari 17 now allows 100 calls per 10 seconds, a bit better.
12-
export function getDefaultThrottle() {
13-
if (typeof window === 'undefined') return 50
14-
// https://stackoverflow.com/questions/7944460/detect-safari-browser
15-
// @ts-expect-error
16-
const isSafari = Boolean(window.GestureEvent)
17-
if (!isSafari) {
18-
return 50
19-
}
20-
try {
21-
const match = navigator.userAgent?.match(/version\/([\d\.]+) safari/i)
22-
return parseFloat(match![1]!) >= 17 ? 120 : 320
23-
} catch {
24-
return 320
25-
}
26-
}
27-
28-
export const defaultRateLimit: NonNullable<Options['limitUrlUpdates']> = {
29-
method: 'throttle',
30-
timeMs: getDefaultThrottle()
31-
}
32-
33-
// --
10+
import { defaultRateLimit } from './rate-limiting'
3411

3512
type UpdateMap = Map<string, string | null>
3613
type TransitionSet = Set<React.TransitionStartFunction>

packages/nuqs/src/useQueryState.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import { useAdapter } from './adapters/lib/context'
33
import type { Options } from './defs'
44
import { debug } from './lib/debug'
55
import { debounceController } from './lib/queues/debounce'
6+
import { defaultRateLimit } from './lib/queues/rate-limiting'
67
import {
7-
defaultRateLimit,
88
globalThrottleQueue,
99
type UpdateQueuePushArgs
1010
} from './lib/queues/throttle'

packages/nuqs/src/useQueryStates.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import { useAdapter } from './adapters/lib/context'
33
import type { Nullable, Options, UrlKeys } from './defs'
44
import { debug } from './lib/debug'
55
import { debounceController } from './lib/queues/debounce'
6+
import { defaultRateLimit } from './lib/queues/rate-limiting'
67
import {
7-
defaultRateLimit,
88
globalThrottleQueue,
99
type UpdateQueuePushArgs
1010
} from './lib/queues/throttle'

0 commit comments

Comments
 (0)