[]previous and next page

A hook with an effect: useDebounced

A hook that has to react to a source and clean up is built on createComputation({ kind: "effect" }), which is what useEffect does.

For agentsUpdated

A hook that has to react to a source and clean up is built on createComputation({ kind: "effect" }), which is what useEffect does. The two-stage form runs fn tracked and apply untracked with the value; the function apply returns is its cleanup, run before the next apply and when the owner is disposed.

src/use-debounced.tsts
import { isServer } from "@shadow-js/core";
import {
  type Accessor,
  createComputation,
  createSignalNode,
  currentOwner,
  readSignal,
  runInScope,
  warn,
  writeSignal,
} from "@shadow-js/core/extend";

/** The value of `source`, updated `ms` milliseconds after it stops changing. */
export function useDebounced<T>(source: Accessor<T>, ms: number): Accessor<T> {
  // User callbacks run untracked: reading the initial value must not subscribe the caller.
  const initial = runInScope({ listener: null }, source);
  const output = createSignalNode<T>(initial, Object.is, "debounced");
  const read: Accessor<T> = () => readSignal(output);
  // Effects never run on the server (useEffect does the same): nothing to schedule there.
  if (isServer) return read;
  // The same diagnostic useEffect raises: this effect could never be disposed.
  if (currentOwner() === null) warn("NO_OWNER_EFFECT");
  createComputation({
    kind: "effect",
    name: "useDebounced",
    defer: true, // the first apply would only repeat `initial`
    fn: source,
    apply: (value: T) => {
      const timer = setTimeout(() => {
        writeSignal(output, value);
      }, ms);
      return () => {
        clearTimeout(timer);
      };
    },
  });
  return read;
}