use-debounced
Create a readonly debounced view of a reactive source.
| Package | @vizejs/composable/use-debounced |
| Own the source | vize lib pull composable:use-debounced |
| Runtime exports | useDebounced |
| Gzip budget | 2048 B |
Minimal setup
Use a Vue 3.5+ project with Vue SFC compilation configured (see Vite+ integration).
vp install @vizejs/composable
Import the entry directly. Call lifecycle-bound utilities inside <script setup> so cleanup follows the component scope; consult the runtime contract for this entry's SSR and hydration behavior.
import { useDebounced } from "@vizejs/composable/use-debounced";
Try it
Search the guide catalogue
Update local search results only after typing stops, with explicit pending, flush, cancel, and empty states.
What to observe: Type component. The input changes immediately while the result list waits 500 ms. Search now applies it immediately; Cancel keeps the last settled results. Try a term with no matches.
query is the writable source; debounced is its readonly delayed view. This example filters local data and sends no network request. Call inside setup so its watcher and timer belong to the component scope. SSR mirrors the source without starting a timer.
Compare the initial and interacted states


Copy the example
Save this complete SFC in your Vue project. Its public imports and source are the same ones executed in the preview.
<script setup lang="ts">
import { computed, ref, useId } from "vue";
import { useDebounced } from "@vizejs/composable/use-debounced";
const query = ref("");
const queryId = useId();
const { debounced, pending, flush, cancel } = useDebounced(query, 500);
const guides = [
"Getting started",
"Component styling",
"Composable reference",
"Keyboard accessibility",
];
const results = computed(() =>
guides.filter((guide) => guide.toLowerCase().includes(debounced.value.toLowerCase())),
);
</script>
<template>
<div class="composable-example">
<label :for="queryId">Find a guide</label>
<input :id="queryId" v-model="query" type="search" placeholder="Try component" />
<output aria-live="polite">{{
pending ? "Waiting for typing to stop…" : `Showing results for: ${debounced || "all guides"}`
}}</output>
<div class="example-actions">
<button type="button" :disabled="!pending" @click="() => flush()">Search now</button>
<button type="button" :disabled="!pending" @click="() => cancel()">
Cancel pending search
</button>
</div>
<ul aria-label="Matching guides">
<li v-for="guide in results" :key="guide">{{ guide }}</li>
</ul>
<p v-if="results.length === 0">No guides match this search.</p>
</div>
</template>
Runtime contract
| Utility | Category | Stability | SSR | Hydration | Cleanup | Targets | Host globals | Uses |
|---|---|---|---|---|---|---|---|---|
useDebounced |
timing | experimental | deterministic-fallback | stable | caller, reactive-scope | web, server, worker, native, desktop, terminal | globalThis, window |
tryOnScopeDispose |
API
useDebounced
Create a readonly debounced view of a reactive source. The view starts at the current source value. Each source change (observed with flush: "sync", so every synchronous write counts) restarts a single-shot timer of waitMs milliseconds; when it fires, the view takes the source value current at that moment. waitMs is reactive and is read when a timer is scheduled; changing it does not restart an already-pending timer. A wait of 0 still defers to the next scheduler tick. Server rendering is explicit: without a browser window (and with UseDebouncedOptions.runOnServer disabled) no timer ever starts and the view mirrors the source synchronously, so server-rendered output shows current values and nothing leaks. pending stays false and the controls report false in that mode. Cleanup rule: the watcher and any pending timer are released when the owning reactive scope stops; call inside an active scope. Outside one, the watcher lives as long as the source and cancel() only clears the pending timer.
function useDebounced<Value>( source: MaybeRefOrGetter<Value>, waitMs: MaybeRefOrGetter<number>, options: UseDebouncedOptions = {}, ): DebouncedControls<Value>
const query = shallowRef("");
const { debounced, flush } = useDebounced(query, 300);
query.value = "vize"; // debounced.value still "" for 300ms
flush(); // debounced.value === "vize" immediately
Types
UseDebouncedOptions
Options for useDebounced.
| Member | Type | Description |
|---|---|---|
runOnServer? |
boolean |
Applies the timing policy when no browser window is available. Keep this disabled during server rendering, where the debounced view mirrors the source synchronously instead of starting timers. Enable it for native, desktop, worker, and test runtimes whose scheduler is lifecycle-bound. |
scheduler? |
TimeoutScheduler |
Owns the single-shot timer. |
DebouncedControls
Reactive debounced view and controls returned by useDebounced.
| Member | Type | Description |
|---|---|---|
debounced |
Readonly<ShallowRef<Value>> |
Readonly view of the source that settles waitMs after the last change. |
pending |
Readonly<ShallowRef<boolean>> |
Whether a trailing update is currently scheduled. |
cancel |
() => boolean |
Discard the scheduled trailing update and keep the last settled value. Later source changes debounce again as usual. |
flush |
() => boolean |
Apply the current source value immediately instead of waiting out the delay. |