Skip to main content

Register and call typed hooks

Define a hook contract using a TypeScript interface to ensure type safety when registering and invoking hooks in hookable. By passing this interface to createHooks, the resulting Hookable instance enforces that only valid hook names are used and that handlers receive the correct argument types.

import { createHooks } from 'hookable'

// 1. Define the hook contract
interface MyHooks {
'render:before': (content: string) => void
'render:after': (content: string, duration: number) => Promise<void> | void
}

// 2. Create a typed Hookable instance
const hooks = createHooks<MyHooks>()

// 3. Register handlers
hooks.hook('render:before', (content) => {
console.log(`Preparing to render: ${content}`)
})

hooks.hook('render:after', async (content, duration) => {
console.log(`Rendered in ${duration}ms`)
})

// 4. Call hooks sequentially
async function performRender(data: string) {
await hooks.callHook('render:before', data)

const start = Date.now()
// ... rendering logic ...
const end = Date.now()

await hooks.callHook('render:after', data, end - start)
}

performRender('Hello World')

The hook method returns an unregister function. Invoking this function removes the specific handler from the Hookable instance, preventing it from being executed in subsequent callHook dispatches. This is useful for cleanup in component lifecycles or temporary event listeners.

import { createHooks } from 'hookable'

interface LifecycleHooks {
'app:error': (error: Error) => void
}

const hooks = createHooks<LifecycleHooks>()

function monitorErrors() {
// Capture the unregister function returned by .hook()
const unregister = hooks.hook('app:error', (err) => {
console.error('Caught error:', err.message)
})

// Return a cleanup function that invokes unregister
return () => {
unregister()
}
}

const stopMonitoring = monitorErrors()

// This will trigger the handler
await hooks.callHook('app:error', new Error('Initial failure'))

// Remove the handler
stopMonitoring()

// This will no longer trigger the handler
await hooks.callHook('app:error', new Error('Subsequent failure'))

When using callHook, handlers are executed sequentially in the order they were registered. If a handler returns a Promise, hookable awaits it before moving to the next handler. Note that if any handler throws an error or returns a rejected Promise, the callHook promise itself rejects immediately, and any remaining handlers in the queue are not executed. Always ensure callHook is awaited to properly handle these potential rejections.