Lib
Compose Events Utility
Combine multiple event handlers into a single function, with optional defaultPrevented checks to control execution order.
Compose Events Utility
composeEventHandlers lets you safely combine multiple event handlers into one, keeping the original component’s handler behavior while layering additional logic. It’s especially useful when you need to augment a component’s events without overriding them.
The original handler runs first. By default, your handler only runs if the event’s default action wasn’t prevented (event.defaultPrevented === false). You can opt out of this behavior.
Why use it?
- Layer behavior without losing the original handler
- Respect defaultPrevented to avoid unintended side effects
- Keep event composition consistent across your codebase
API
function composeEventHandlers<E>(
originalEventHandler?: (event: E) => void,
ourEventHandler?: (event: E) => void,
options?: { checkForDefaultPrevented?: boolean } // default: true
): (event: E) => void;Prop
Type
Usage
Augment an existing click handler
import * as React from 'react';
import { composeEventHandlers } from '@loke/ui/compose-events';
type Props = React.ButtonHTMLAttributes<HTMLButtonElement> & {
onConfirmClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
};
export function ConfirmButton({ onClick, onConfirmClick, ...props }: Props) {
const handleClick = composeEventHandlers<React.MouseEvent<HTMLButtonElement>>(
// original: if consumer passed onClick, it runs first
onClick,
// ours: runs if original did not preventDefault()
(e) => {
onConfirmClick?.(e);
// Optionally: do more logic here
}
);
return (
<button type="button" {...props} onClick={handleClick}>
Confirm
</button>
);
}In this example:
- If the consumer’s
onClickcallse.preventDefault(), thenonConfirmClickwon’t run (default behavior). - If you want
onConfirmClickto always run, pass{ checkForDefaultPrevented: false }.
Always run the secondary handler
const handleClick = composeEventHandlers<React.MouseEvent<HTMLButtonElement>>(
onClick,
(e) => {
// This will run even if original prevented default
onConfirmClick?.(e);
},
{ checkForDefaultPrevented: false }
);Prevent a secondary handler based on validation
function ValidatedLink({
onClick,
validate,
...props
}: React.AnchorHTMLAttributes<HTMLAnchorElement> & {
validate?: () => boolean;
}) {
const handleClick = composeEventHandlers<React.MouseEvent<HTMLAnchorElement>>(
onClick,
(e) => {
if (!validate?.()) {
// Stop navigation and any downstream work
e.preventDefault();
}
}
);
return <a {...props} onClick={handleClick} />;
}Here, validate() can prevent the default action, which suppresses the rest of the composed chain (if there are more handlers later composed using the same pattern).
Patterns
- Compose more than two handlers by chaining:
const h1 = composeEventHandlers(a, b); const h2 = composeEventHandlers(h1, c); // a -> b -> c - Use generic
Eto strongly type synthetic events:React.MouseEvent<HTMLButtonElement>React.KeyboardEvent<HTMLInputElement>React.FocusEvent<HTMLElement>
- Keep side effects in the “our” handler and let the original handler retain first say on preventing defaults.
Tips
- The default behavior (checking
defaultPrevented) is a sensible guard that prevents accidental duplicate side effects. Only opt out when you really need both handlers to run no matter what. - This utility is ideal for component libraries: you can safely expose
onXprops while still applying library-specific logic.