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.
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;
}