createEventDispatcher
creates a custom-event dispatcher for emitting component events.
SolidJS equivalent of Svelte's homonymous function.
npm install @solid-primitives/event-dispatcher
# or
yarn add @solid-primitives/event-dispatcher
import { createEventDispatcher } from "@solid-primitives/event-dispatcher";
interface Props {
onCustomMessage: (evt: CustomEvent<string>) => void;
}
function ChildComponent(props: Props) {
const dispatch = createEventDispatcher(props);
return (
<button onClick={() => dispatch("customMessage", "yo World!", { cancelable: true })}>
send
</button>
);
}
createEventDispatcher
takes one argument, the component's props
, and returns an event dispatcher function. props
must be passed as they are, without changing or spreading spreading them, in order to maintain their reactivity.
The resulting event dispatcher is named by convention dispatch
, and will create a DOM custom event (CustomEvent<T>
) and call the associated event handler. It takes 3 arguments:
- the event name (
name: string
), in lower camel case. E.g,customMessage
. When dispatching the event, the dispatcher will look for the "on
+ upper camel case name in the props (onCustomMessage
). - the payload (
payload?: any
), the payload associated to the event. This value is optional, and will be accessible in theCustomEvent.detail
property. - custom event options (
dispatcherOptions: { cancelable: boolean }
). The dispatcherOptions is an object with one property,cancelable
, which determines whether the created custom event is cancelable (meaning itspreventDefault()
method can be called). This arguments is optional and defaults to{ cancelable: false }
.
To parallel DOM's dispatchEvent
, dispatch
will return false
, if the custom event is cancelable and preventDefault()
has been called, true
in all the other cases (even if there is no event handler associated to the event).
const submitCustomForm = () => {
const dispatched = dispatch("customSubmit", data(), { cancelable: true });
if (!dispatched) return;
apiPost(url(), data());
};
Finally, the custom events created with dispatch
don't bubble.
function ParentComponent() {
function handleMessage(evt: CustomEvent<string>) {
console.log("the message is " + evt.detail);
}
return <Child onCustomMessage={handleMessage} />;
}
The parent component will be able to listen to any event dispatched by its child component as it would listen to any DOM event: by passing a "on
+ capitalized name of the event" prop to the child, with the event handler function.
If the child component dispatched any payload with the event, the handler will be able to access it in event.detail
. The handler will also be able to call event.preventDefault()
if the event is cancellable.
You won't have to worry about checking if an optional event handler was passed or not in the props, as dispatch will do it under the hood, avoiding the Uncaught TypeError: props.onOptionalEvent is not a function
.
// without createEventDispatcher
<button onClick={() => {
if (props.onOptionalEvent) {
props.onOptionalEvent()
}
}}>emit</button>
// with createEventDispatcher
<button onClick={() => dispatch('optionalEvent')}>emit</button>
createEventDispatcher
has full TypeScript support (from version 4.1.5, as it uses template literals types
and the infer
keyword). In order to benefit from it, you need to type the component props.
With regards to the typing:
- In the props type or interface, there should be an event listener prop for each component event dispatched. This event listener key corresponds to
on
+ capitalized event name. - The value of each event listener is an event handler function, which takes one parameter, the
CustomEvent
, or none. - The payload type is passed as argument to the component event type.
// if we will call dispatch('componentEvent', 'I am in event.detail')
interface Props {
onComponentEvent: (evt: CustomEvent<string>) => void;
nonEventProp: string;
}
When the dispatcher is created, and you start typing dispatch()
TypeScript will suggest a list of the available event, which will be inferred by the props. The props that don't begin with on
will be ignored.
interface Props {
onFirstComponentEvent: (evt: CustomEvent<string>) => void;
onSecondComponentEvent: (evt: CustomEvent<number>) => void;
nonEventProp: string;
}
// in the component
dispatch(""); // => will suggest "firstComponentEvent"|"secondComponentEvent" as first parameter
Once you have chosen the event, TypeScript will suggest the payload type for that specific event, and show an error if your payload is of the wrong type.
interface Props {
onStringEvent: (evt: CustomEvent<string>) => void;
onNumberEvent: (evt: CustomEvent<number>) => void;
}
// in the component
dispatch("stringEvent"); // => will suggest "(eventName: "changeStep", payload: string, dispatcherOptions?: DispatcherOptions | undefined) => boolean"
dispatch("numberEvent", "forty-two"); // will throw "Error: Argument of type 'string' is not assignable to parameter of type 'number'."
Finally, TypeScript will handle suggestions and errors also according to weather the event is otpional or not, requesting to pass a second argument (the payload
), when the event is non-nullable.
interface Props {
onMandatoryPayload: (evt: CustomEvent<number>) => void;
onOptionalPayload: (evt?: CustomEvent<string>) => void;
}
// in the component
dispatch("mandatoryPayload"); // => will throw: "Error: Expected 2-3 arguments, but got 1."
dispatch("optionalPayload"); // will not complain, but suggest "(eventName: "optionalPayload", payload?: number | undefined, ...
You can use this template for publishing your demo on CodeSandbox: https://codesandbox.io/s/solid-create-event-dispatcher-example-fbj9ge
See CHANGELOG.md