Effects

useDebounceValue

State whose debounced copy updates after a pause.

Demo

live
debounced

settled

Usage

use-debounce-value.tsxtsx
import { useState } from "react";
import { useDebounceValue } from "hookli";

export function Demo() {
  const [text, setText] = useState("");
  const [debounced, setValue] = useDebounceValue("", 500);

  return (
    <div>
      <input
        value={text}
        onChange={(e) => {
          setText(e.target.value);
          setValue(e.target.value);
        }}
      />
      <p>Debounced: {debounced}</p>
    </div>
  );
}

API

useDebounceValue.d.tsts
useDebounceValue<T>(initialValue: T | (() => T), delayMs?: number, options?: DebounceOptions & { equalityFn?: (left: T, right: T) => boolean }): [T, DebouncedState<[T | ((prev: T) => T)], void>]

Parameters

NameTypeDefaultDescription
initialValueT | (() => T)The starting value, or a factory evaluated once on mount — the same lazy-initializer contract as useState.
delayMsnumber500Milliseconds of inactivity before the debounced value catches up to the latest set value.
optionsDebounceOptions & { equalityFn? }{}DebounceOptions plus an optional equalityFn (left, right) => boolean — when it reports the values equal, the debounced update is skipped. See the tables below.

Returns

NameTypeDescription
[0] debouncedValueTThe value that trails the setter, updating only after delayMs of quiet.
[1] setValueDebouncedState<[T | ((prev: T) => T)], void>A debounced setter accepting a value or updater; it also carries cancel, flush and isPending (see DebouncedState).

DebounceOptions

Controls how the debounced invocation is scheduled.

NameTypeDescription
leadingbooleanInvoke on the leading edge — run once immediately on the first call of a burst.
trailingbooleanInvoke on the trailing edge — run after the burst settles.
maxWaitnumberThe maximum time the callback may be delayed before it is forced to run, even during a continuous burst.

DebouncedState<Args, R>

The debounced function, plus manual control methods.

NameTypeDescription
(...args)(...args: Args) => R | undefinedCalling it schedules an invocation and returns the last computed result — undefined before the first run.
cancel() => voidCancels any pending trailing invocation.
flush() => R | undefinedImmediately runs any pending invocation and returns its result.
isPending() => booleanWhether a trailing invocation is currently scheduled.

Hook

The implementation, straight from hookli. Copy it, or install the package.

src/hooks/use-debounce-value/use-debounce-value.tstsx
import { useEffect, useRef, useState } from "react";
import {
  useDebounceCallback,
  type DebounceOptions,
  type DebouncedState,
} from "../use-debounce-callback/use-debounce-callback";

export type UseDebounceValueReturn<T> = [
  T,
  DebouncedState<[value: T | ((prev: T) => T)], void>,
];

export const useDebounceValue = <T>(
  initialValue: T | (() => T),
  delayMs = 500,
  options: DebounceOptions & { equalityFn?: (left: T, right: T) => boolean } = {},
): UseDebounceValueReturn<T> => {
  const eq = options.equalityFn ?? ((left, right) => left === right);
  const unwrap = (v: T | (() => T)): T =>
    typeof v === "function" ? (v as () => T)() : v;

  const [debouncedValue, setDebouncedValue] = useState<T>(() =>
    unwrap(initialValue),
  );
  const previousValue = useRef(debouncedValue);

  const updateDebouncedValue = useDebounceCallback(
    (value: T | ((prev: T) => T)) => {
      const next =
        typeof value === "function"
          ? (value as (prev: T) => T)(previousValue.current)
          : value;
      if (!eq(previousValue.current, next)) {
        previousValue.current = next;
        setDebouncedValue(next);
      }
    },
    delayMs,
    options,
  );

  useEffect(() => {
    return () => updateDebouncedValue.cancel();
  }, [updateDebouncedValue]);

  return [debouncedValue, updateDebouncedValue];
};

View source on GitHub

Related hooks