Vize

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.

Open the interactive example

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
Search the guide catalogue: initial state
Initial state
Search the guide catalogue: state after the verified interactions
After the verified interactions

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.