-
-
Notifications
You must be signed in to change notification settings - Fork 122
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Debounce URL updates #291
Comments
I would argue that this is not the scope of this library, there are hooks that allow debouncing and throttling methods: |
Alright, fair enough, thx 👍 |
FYI, this was implemented in 1.8+, since changes to the history API are rate-limited by the browser, throttling was actually necessary for all updates. |
For debouncing:
|
@rwieruch one issue with debouncing the call to setState is, like React.useState, you'd also delay the internal state representation. If using a controlled
|
I see, thanks for the nudge in the right direction! Would it make sense to have a |
Just came across this - a built in I see that there are other ways to achieve it (e.g. (Thanks for the great library by the way!) |
+1 for I'm managing some query filters and a debounced search box using router query state. On initial testing the package works brilliantly for this - except that using I think Potential example with const searchQuery = router.query.search;
const [search, setSearch] = useQueryState('search', { debounceMs: 500 });
const { data } = useGetDataQuery({ search: searchQuery }); // RTKQ
//...
<input value={search} onChange={(e) => setSearch(e.target.value)} /> Example without const [search, setSearch] = useQueryState('search');
// Have to manually control query here
const [getData, { data }] = useLazyGetDataQuery();
useEffect(() => {
const debounce = setTimeout(() => {
getData({ search });
}, 500);
return () => {
clearTimeout(debounce);
};
}, [getData, search]);
//...
<input value={search} onChange={(e) => setSearch(e.target.value)} /> |
I see two issues with supporting both throttling and debouncing. The first is the API: providing both .withOptions({
limitUrlUpdates: { // better be explicit in what this does
method: 'throttle' | 'debounce',
timeMs: 500
}
})
// Or with helpers:
.withOptions({
limitUrlUpdates: debounce(500),
limitUrlUpdates: throttle(500)
}) The second point is closely related: both throttle and debounce methods will actually run in parallel, for different hook setups to work together. Example: const [, setFoo] = useQueryState('foo', { limitUrlUpdates: debounce(500) })
const [, setBar] = useQueryState('bar', { limitUrlUpdates: throttle(500) })
const doubleUpdate = () => {
setFoo('foo') // This will update in 500ms
setBar('bar') // This will update immediately
} I'll see what I can do to refactor the URL update queue system to account for both methods, but this will conflict with the Promise returned by the state updater functions being cached until the next update. Not sure how big a deal this is: hooks set to throttle will return one Promise, hooks set to debounce will return another. If two hooks are set to debounce with different times, just like throttle, the largest one wins: const [, setA] = useQueryState('a', { limitUrlUpdates: debounce(200) })
const [, setB] = useQueryState('b', { limitUrlUpdates: debounce(100) })
const doubleUpdate = () => {
setA('a')
setB('b')
// Both will be applied in 200ms if there are no other updates.
} |
Hm. Will the next version be a breaking change anyway? One could consider only supporting debounce and not throttle anymore. But one would have to get some user data here whether debounce is more widely used for URL state. |
I believe both methods are justified, it's not a deal breaker, just a bit of refactoring work on the internals. As for the breaking change part, yes this would probably land in v2, or it could be done in a non-breaking way by deprecating the I would still keep throttling as the default, as it is more reactive and predictable. Before shallow routing was introduced, the delayed update of the URL was a side effect of the network call to the server to update RSCs, and it looked sluggish and people complained. |
I appreciate this getting looked at! In the mean time, you can still get debouncing with high-frequency inputs by using
|
+1 for debounce |
I'm planning on working on this during the summer holidays, if I can find a bit of free time. It also depends on the Next.js 15 / React 19 release schedule. |
+1 for debounce. Would be super helpful for a search input, to not hit the server with every keystroke :) Btw: Awesome library! ✌️ |
To avoid sending on every keystroke, you can use the Now that v2 is out, debounce support is back on the roadmap! |
@TkDodo talked at react advance yesterday a little about why react-query chose to not implement debouncing in the core library itself: youtube link The key points from what I could tell:
I don't personally have strong opinions one way or the other, but react-query's experience with |
Thanks for the link @tylersayshi ! One key difference here I think, is that in the example given for RQ, the thing to rate-limit is truly external to the library: it's an input passed to the query key and to other options. In this case, composition is indeed a much better API. The difference in nuqs is that the thing to rate-limit is not exposed to the user: we're not talking about rate-limiting updates of the state value returned by the hook (this can, and should, be done in userland like for RQ), but rate-limiting the sync mechanism that writes to the URL, because of limits imposed by browser vendors. Since those updates occur outside of the render tree, traditional composition (via hooks) doesn't apply. We could allow some sort of callback mechanism to define custom methods of rate-limiting, but those likely would be less optimised and would not handle certain cases (eg: setting multiple query states in the same tick batches them together into a single URL update). |
Thanks for the clarification! That makes sense to me :) |
Hello,
For now, it cannot be used to save the state of a search input because the URL is updated for each key stroke. It would be nice to debounce or throttle the URL updates for a smoother interaction ;)
The text was updated successfully, but these errors were encountered: