In VueUse, how do refDebounced, useDebounceFn and useThrottleFn differ, and which fits a dashboard's search box versus its resize handler?
answer
- a ref versus a function
- quiet period versus fixed rate
- cancel, flush and isPending
- superseded calls resolve empty
- trailing default flipped in 15
basics
~20 srefDebounced returns a read-only ref that copies a source ref once it stops changing; useDebounceFn wraps a function so it runs after calls pause; useThrottleFn runs a function at most once per interval. Debounce the search box; throttle the resize handler.
solid answer
~40 s`refDebounced(search, 300)` gives a read-only ref that takes `search`'s value 300 ms after the last change — ideal for a search box whose debounced value a `watch` turns into a request. `useDebounceFn(fn, ms, { maxWait, rejectOnCancel })` debounces a function instead; it returns a promise-returning wrapper with `cancel()`, `flush()` and an `isPending` ref, and a call superseded by a newer one resolves with `undefined` unless `rejectOnCancel` is set. `useThrottleFn(fn, ms)` runs at most once per interval, with `leading` and, since VueUse 15, `trailing` both defaulting to true, so the final resize of a burst is still handled. None of them cancels a pending timer on unmount by itself; call `cancel()` or `flush()` in `onUnmounted` when it matters. All three default to 200 ms.
code
vue · 18 lines<script setup lang="ts">
import { shallowRef, watch, onUnmounted } from 'vue'
import { refDebounced, useDebounceFn, useThrottleFn, useEventListener } from '@vueuse/core'
const props = defineProps<{ layout: PanelLayout }>()
// search box: the query follows typing after a 300 ms pause
const search = shallowRef('')
const debouncedSearch = refDebounced(search, 300)
watch(debouncedSearch, (q) => loadReports(q))
// layout save: after a 1 s pause, but at least every 5 s while dragging
const saveLayout = useDebounceFn(() => api.saveLayout(props.layout), 1000, { maxWait: 5000 })
onUnmounted(() => saveLayout.flush())
// resize: at most every 150 ms; the final event still runs (trailing)
useEventListener('resize', useThrottleFn(measurePanels, 150), { passive: true })
</script>go deeper
Recall which helper takes a ref and which takes a function, and that search boxes are debounced while resize handlers are throttled.
Explain the controls on useDebounceFn — cancel, flush, isPending, maxWait — and what a superseded call's promise resolves to.
Show you handle lifecycle and version edges: flush or cancel pending timers on unmount, and know that trailing throttle became the default in VueUse 15.
Frame timing helpers as UX policy: agree delays and flush-on-leave rules per interaction, and recheck them whenever a VueUse major changes defaults.
## Two timing policies A **debounce** waits for a quiet period: every new call restarts the timer, and the work runs once the calls stop. A **throttle** enforces a rate: the work runs at most once per interval, however many calls arrive. VueUse packages both, in a form that fits reactive code, and the interview question is which helper to use where and what its defaults are. ## The three helpers | Helper | Input | Returns | Default delay | Typical use | |---|---|---|---|---| | `refDebounced(source, ms?, options?)` | a `Ref` | read-only ref that lags the source | 200 ms | debounced value to watch or render | | `useDebounceFn(fn, ms?, options?)` | a function | wrapper returning a promise, plus `cancel`, `flush`, `isPending` | 200 ms | debounced side effect such as a save | | `useThrottleFn(fn, ms?, trailing?, leading?, rejectOnCancel?)` | a function | wrapper returning a promise | 200 ms | rate-limited handler for resize or scroll | `refThrottled` is the ref-shaped sibling of `useThrottleFn`. The older names `useDebounce` and `debouncedRef` are deprecated aliases of `refDebounced`; the same goes for `useThrottle` and `throttledRef` pointing at `refThrottled`. ## The dashboard's search box The input is bound to a ref. `refDebounced(search, 300)` produces a second ref that only updates after typing pauses, and a `watch` on it triggers the report query. Because the debounced ref is **read-only**, nothing can write around the debounce, and the template can still show the live `search` value while the query waits. When the thing to debounce is an action rather than a value — saving a dashboard layout after drag-and-drop — `useDebounceFn` fits better, and its controls matter: - `maxWait` forces a run after that many milliseconds even if calls never pause, so a user dragging continuously still gets saved. - `isPending` is a ref that is true while a run is scheduled — handy for a "saving…" hint. - `flush()` runs the pending call now, for example before navigating away; `cancel()` drops it. - Each call returns a **promise** of the function's result. A call replaced by a newer one resolves with `undefined` without running, unless `rejectOnCancel: true` makes it reject instead. ## The resize handler A panel that re-measures charts on `resize` should update **during** the drag, not only after it, so it wants a throttle: `useThrottleFn(measurePanels, 150)` passed to `useEventListener('resize', …)`. The defaults are the part candidates misremember: 1. `leading` defaults to **true**: the first event of a burst runs immediately. 2. `trailing` defaults to **true** since VueUse 15: a call that arrives inside the wait window is run once the window ends, so the final size is always measured. 3. Before VueUse 15 the trailing default was false, and the last resize of a quick drag could be dropped, leaving charts sized for an intermediate width. ## Lifecycle: what is not automatic Listeners and observers in VueUse clean up with their component, but the debounce and throttle timers do **not** cancel themselves on unmount. A save scheduled 1 s before the user leaves the dashboard still fires afterwards. Decide explicitly: - call `flush()` in `onUnmounted` when the pending work must happen (saving a layout); - call `cancel()` when it must not (a search for a panel that no longer exists). ## Choosing quickly - The **value** should lag behind typing: `refDebounced`. - A **side effect** should run once activity pauses: `useDebounceFn`. - A **handler** should keep up at a bounded rate: `useThrottleFn`. ## Common mistakes - **Creating the wrapper per event.** Writing `@input="useDebounceFn(search, 300)()"` builds a new debouncer with its own timer on every keystroke, so nothing is debounced; create the wrapper once in `setup`. - **Debouncing the resize handler.** Charts then keep their old size for the whole drag and jump at the end; a throttle updates during the drag. - **Throttling the search box.** Requests go out every interval while the user is still typing, most of them for half-typed words. - **Expecting a debounce to cancel network work.** `refDebounced` only delays a value; a request already sent keeps running. Aborting belongs to the fetching code — VueUse's `useFetch` aborts its previous request when it refetches. - **Reading `isPending` from `refDebounced`.** The pending flag, `cancel()` and `flush()` live on the function returned by `useDebounceFn`, not on the debounced ref.
- Why is the ref returned by VueUse's refDebounced read-only?It is a derived value that only the debounce timer should update. Making it read-only stops code from writing around the debounce; you write to the source ref, and the debounced ref follows after the quiet period.
- What does maxWait add to VueUse's useDebounceFn?An upper bound on the delay. With `maxWait: 5000`, continuous calls that never pause still run the function at least once every 5 s, so a user who keeps dragging a panel still gets periodic saves instead of none.
- What does awaiting a superseded useDebounceFn call return, and how do you detect cancellation?By default the superseded call's promise resolves with `undefined` and the function does not run for it. Pass `rejectOnCancel: true` if the caller must distinguish a cancelled call; then the promise rejects instead.
saying these in an interview costs you the question
- refDebounced returns a function you call to push the value through.
- useThrottleFn drops the last call of a burst by default in VueUse 15.
- A superseded useDebounceFn call rejects with an error by default.
- VueUse cancels pending debounce timers automatically when the component unmounts.
- useDebounce is a separate, newer API that replaced refDebounced.