@@ -130,43 +130,145 @@ useQueryState('foo', { scroll: true })
130
130
```
131
131
132
132
133
- ## Throttling URL updates
133
+ ## Rate-limiting URL updates
134
134
135
135
Because of browsers rate-limiting the History API, updates ** to the
136
136
URL** are queued and throttled to a default of 50ms, which seems to satisfy
137
137
most browsers even when sending high-frequency query updates, like binding
138
138
to a text input or a slider.
139
139
140
- Safari's rate limits are much higher and require a throttle of 120ms (320ms for older
141
- versions of Safari).
140
+ Safari's rate limits are much higher and use a default throttle of 120ms
141
+ (320ms for older versions of Safari).
142
142
143
- If you want to opt-in to a larger throttle time -- for example to reduce the amount
143
+ <Callout title = " Note" >
144
+ the state returned by the hook is always updated ** instantly** , to keep UI responsive.
145
+ Only changes to the URL, and server requests when using ` shallow: false{:ts} ` , are throttled.
146
+ </Callout >
147
+
148
+ This throttle time is configurable, and also allows you to debounce updates
149
+ instead.
150
+
151
+ <Callout title = " Which one should I use?" >
152
+ Throttle will emit the first update immediately, then batch updates at a slower
153
+ pace ** regularly** . This is recommended for most low-frequency updates.
154
+
155
+ Debounce will push back the moment when the URL is updated when you set your state,
156
+ making it ** eventually consistent** . This is recommended for high-frequency
157
+ updates where the last value is more interesting than the intermediate ones,
158
+ like a search input or moving a slider.
159
+
160
+ Read more about [ debounce vs throttle] ( https://kettanaito.com/blog/debounce-vs-throttle ) .
161
+ </Callout >
162
+
163
+ ### Throttle
164
+
165
+ If you want to increase the throttle time -- for example to reduce the amount
144
166
of requests sent to the server when paired with ` shallow: false{:ts} ` -- you can
145
- specify it under the ` throttleMs ` option:
167
+ specify it under the ` limitUrlUpdates ` option:
146
168
147
- ``` tsx
148
- // [!code word:throttleMs]
169
+ ``` ts /limitUrlUpdates/
149
170
useQueryState (' foo' , {
150
171
// Send updates to the server maximum once every second
151
172
shallow: false ,
152
- throttleMs: 1000
173
+ limitUrlUpdates: {
174
+ method: ' throttle' ,
175
+ timeMs: 1000
176
+ }
153
177
})
154
- ```
155
178
156
- <Callout title = " Note" >
157
- the state returned by the hook is always updated ** instantly** , to keep UI responsive.
158
- Only changes to the URL, and server requests when using ` shallow: false{:ts} ` , are throttled.
159
- </Callout >
179
+ // or using the shorthand:
180
+ import { throttle } from ' nuqs'
181
+
182
+ useQueryState (' foo' , {
183
+ shallow: false ,
184
+ limitUrlUpdates: throttle (1000 )
185
+ })
186
+ ```
160
187
161
188
If multiple hooks set different throttle values on the same event loop tick,
162
189
the highest value will be used. Also, values lower than 50ms will be ignored,
163
190
to avoid rate-limiting issues.
164
191
[ Read more] ( https://francoisbest.com/posts/2023/storing-react-state-in-the-url-with-nextjs#batching--throttling ) .
165
192
166
- Specifying a ` +Infinity{:ts} ` value for ` throttleMs{:ts} ` will ** disable** updates to the
193
+ Specifying a ` +Infinity{:ts} ` value for throttle time will ** disable** updates to the
167
194
URL or the server, but all ` useQueryState(s) ` hooks will still update their
168
195
internal state and stay in sync with each other.
169
196
197
+ <Callout title = " Deprecation notice" >
198
+ The ` throttleMs ` option has been deprecated in ` nuqs@2.4.0 ` and will be removed
199
+ in a later major upgrade.
200
+
201
+ To migrate:
202
+ 1 . ` import { throttle } from 'nuqs' {:ts} `
203
+ 2 . Replace ` { throttleMs: 100 }{:ts} ` with ` { limitUrlUpdates: throttle(100) }{:ts} ` in your options.
204
+ </Callout >
205
+
206
+ ### Debounce
207
+
208
+ In addition to throttling, you can apply a debouncing mechanism to state updates,
209
+ to delay the moment where the URL gets updated with the latest value.
210
+
211
+ This can be useful for high frequency state updates where you only care about
212
+ the final value, not all the intermediary ones while typing in a search input
213
+ or moving a slider.
214
+
215
+ We recommend you opt-in to debouncing on specific state updates, rather than
216
+ defining it for the whole search param.
217
+
218
+ Let's take the example of a search input. You'll want to update it:
219
+
220
+ 1 . When the user is typing text, with debouncing
221
+ 2 . When the user clears the input, by sending an immediate update
222
+ 3 . When the user presses Enter, by sending an immediate update
223
+
224
+ You can see the debounce case is the outlier here, and actually conditioned on
225
+ the set value, so we can specify it using the state updater function:
226
+
227
+ ``` tsx
228
+ import { useQueryState , parseAsString , debounce } from ' nuqs' ;
229
+
230
+ function Search() {
231
+ const [search, setSearch] = useQueryState (
232
+ ' q' ,
233
+ parseAsString .withDefault (' ' ).withOptions ({ shallow: false })
234
+ )
235
+
236
+ return (
237
+ <input
238
+ value = { search }
239
+ onChange = { (e ) =>
240
+ setSearch (e .target .value , {
241
+ // Send immediate update if resetting, otherwise debounce at 500ms
242
+ limitUrlUpdates: e .target .value === ' ' ? undefined : debounce (500 )
243
+ })
244
+ }
245
+ onKeyPress = { (e ) => {
246
+ if (e .key === ' Enter' ) {
247
+ // Send immediate update
248
+ setSearch (e .target .value )
249
+ }
250
+ }}
251
+ />
252
+ )
253
+ }
254
+ ```
255
+
256
+ ### Resetting
257
+
258
+ You can use the ` defaultRateLimit{:ts} ` import to reset debouncing or throttling to
259
+ the default:
260
+
261
+ ``` ts /defaultRateLimit/
262
+ import { debounce , defaultRateLimit } from ' nuqs'
263
+
264
+ const [, setState] = useQueryState (' foo' , {
265
+ limitUrlUpdates: debounce (1000 )
266
+ })
267
+
268
+ // This state update isn't debounced
269
+ setState (' bar' , { limitUrlUpdates: defaultRateLimit })
270
+ ```
271
+
170
272
171
273
## Transitions
172
274
0 commit comments