Skip to main content

Register and call typed hooks

Defining a TypeScript interface for your hooks ensures that every handler registered and every call made to a hook follows a strict contract. By using createHooks, you can instantiate a hook manager that enforces these types, preventing runtime errors caused by mismatched arguments or misspelled hook names.

import { createHooks } from 'hookable';

// 1. Define the hook contract
interface MyHooks {
'app:init': () => void | Promise<void>;
'user:login': (user: { name: string }) => void;
}

async function run() {
// 2. Create a typed Hookable instance
const hooks = createHooks<MyHooks>();

// 3. Register handlers with type safety
hooks.hook('app:init', async () => {
// Observable effect: simulating an async initialization
await new Promise((resolve) => setTimeout(resolve, 10));
});

hooks.hook('user:login', (user) => {
// Observable effect: user.name is typed as string
console.log(`User ${user.name} logged in`);
});

// 4. Trigger hooks sequentially
// callHook returns a Promise that resolves when all handlers finish
await hooks.callHook('app:init');
await hooks.callHook('user:login', { name: 'Alice' });
}

await run();

The hook method returns an unregister function that allows you to dynamically remove a handler when it is no longer needed. This is useful for cleaning up listeners in short-lived components or temporary processes.

import { createHooks } from 'hookable';

interface LifecycleHooks {
'data:update': (data: string) => void;
}

async function manageLifecycle() {
const hooks = createHooks<LifecycleHooks>();

// Capture the unregister function returned by .hook()
const unregister = hooks.hook('data:update', (data) => {
console.log(`Received: ${data}`);
});

// Invoke the hook; the handler executes
await hooks.callHook('data:update', 'First Update');

// Remove the handler
unregister();

// Invoke the hook again; the handler no longer executes
await hooks.callHook('data:update', 'Second Update');
}

await manageLifecycle();

When using callHook, handlers are executed in the order they were registered. If any handler returns a Promise, callHook waits for that promise to resolve before moving to the next handler or completing the call. If a handler throws an error or returns a rejected promise, the callHook promise itself will reject with that error, allowing you to handle failures at the call site. Registered handlers must produce observable effects, such as modifying an external state or logging, as callHook does not return values from the callbacks.