-
-
Notifications
You must be signed in to change notification settings - Fork 0
Hooks
defineHook({ name?, layers?, setup })Status: Current.
Hooks package reusable reactive logic. Component and hook layer access uses the same alias-record model.
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.
| 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. |
const useThreshold = defineHook<
{ minimum: number },
{ accepted: (value: number) => boolean }
>({
setup({ config }) {
return { accepted: (value) => value >= config.minimum };
},
});Use Config | undefined when a hook has optional configuration. defineHook
then exposes an optional call instead of forcing callers to pass undefined:
const useFeatureFlag = defineHook<
{ initialValue: boolean } | undefined,
ReadonlySignal<boolean>
>({
setup({ config, signal }) {
return signal(config?.initialValue ?? false);
},
});
useFeatureFlag();
useFeatureFlag({ initialValue: true });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.
@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.
Debounce and throttle preserve the source signal value type, including unions, readonly values, and object shapes. Event listeners derive valid event names and callback payloads from their target:
useEventListener({
target: document,
event: 'visibilitychange',
handler: (event) => console.log(event.type), // Event
});
useEventListener({
target: button,
event: 'click',
handler: (event) => console.log(event.clientX), // MouseEvent
});Known DOM targets reject incompatible event names during type checking. Custom
EventTarget implementations accept application-defined string event names
and receive Event.
Effect may power internal hook implementation, but it is not part of the
@effuse/use developer contract. Published ESM and CommonJS declarations use
Effuse-owned tagged unions, constructors, matchers, and native Error shapes;
they contain no imports from Effect. A package build gate enforces this
boundary. Developers can construct and narrow states, use $is and $match,
and catch exported errors without importing or learning Effect.