Skip to content
Chris Michael edited this page Jul 22, 2026 · 8 revisions

Effuse

Hooks

defineHook({ name?, layers?, setup })

Status: Current.

Hooks package reusable reactive logic. Component and hook layer access uses the same alias-record model.

Typed Hook

export const useCurrentUser = defineHook({
  name: 'useCurrentUser',
  layers: { auth: AuthLayer } as const,
  setup({ computed, layers }) {
    const auth = layers.auth.service('auth');
    return {
      label: computed(() => auth.currentUser().name),
    };
  },
});

The layer must already be registered at the application composition root. Missing bindings fail before hook setup executes.

Hook Context

Member Purpose
config Typed input supplied by the caller.
signal, computed Reactive state and derivation.
watchEffect Lifecycle-owned reactive effect with rerun cleanup and an EffectHandle.
onMount Register work with an active component lifecycle.
layers Typed alias or list accessor.
scope Register LIFO finalizers or manually dispose a standalone hook.
use Compose another hook function.
runAsync Execute typed asynchronous work.

Configuration

const useThreshold = defineHook<
  { minimum: number },
  { accepted: (value: number) => boolean }
>({
  setup({ config }) {
    return { accepted: (value) => value >= config.minimum };
  },
});

Cleanup

Hooks called during component setup are owned by that component lifecycle. Unmount stops every hook effect synchronously before asynchronous scope finalizers run. A cleanup returned from watchEffect runs before the next reactive execution and again when the effect stops:

const useWindowEvents = defineHook({
  setup({ watchEffect }) {
    const effect = watchEffect(() => {
      const onResize = () => console.log(window.innerWidth);
      window.addEventListener('resize', onResize);
      return () => window.removeEventListener('resize', onResize);
    });

    return { pause: effect.pause, resume: effect.resume };
  },
});

Use scope.addFinalizer for a resource that must survive effect reruns and end only when the hook is disposed. Finalizers execute once in reverse registration order. Every finalizer is attempted; multiple failures are combined and routed through the lifecycle error handler.

When a hook is invoked without an active component lifecycle, onMount runs immediately and its cleanup belongs to the hook scope. A standalone hook must expose or otherwise arrange scope.dispose() if it allocates long-lived resources. Component-owned hooks need no manual disposal.

If layer validation or hook setup throws, Effuse stops effects immediately, starts best-effort scope cleanup, preserves the original setup error, and reports any asynchronous rollback failure as a lifecycle cleanup error.

Ecosystem Hooks

@effuse/use provides browser-focused hooks for storage, media queries, online state, intervals, debounce/throttle, event listeners, element visibility, window size, and related concerns. Browser-only hooks must retain explicit SSR guards.

Related

Clone this wiki locally