A template event binding runs an Effect when the browser dispatches an event. Use the Effect
directly when it already has the data it needs; use EventHandler.make when the work needs the event.
Building on the search field, first clear a known value, then
read a submitted query without navigating away from the page.
Use a plain Effect when the event carries no needed data
A clear button already knows which state it should change:
import { RefSubject } from "@typed/fx";
import { component, html } from "@typed/template";
export const ClearSearch = component(function* () {
const query = yield* RefSubject.make("scope");
return html`<section>
<output>${query}</output>
<button type="button" onclick=${RefSubject.set(query, "")}>Clear search</button>
</section>`;
});
The Effect is a reusable description. Each click runs it; constructing the template does not run it once at module initialization. There is no need to wrap it in a callback that ignores an event.
Read browser data at the event boundary
A form submission needs data from the form. EventHandler.make receives the event and returns the
Effect to run. This example publishes the submitted query to an output:
import { RefSubject } from "@typed/fx";
import { component, html } from "@typed/template";
import * as EventHandler from "@typed/template/EventHandler";
export const SearchForm = component(function* () {
const submitted = yield* RefSubject.make("");
const submit = EventHandler.make(
(event: SubmitEvent) => {
const form = event.currentTarget as HTMLFormElement;
const query = new FormData(form).get("query");
return RefSubject.set(submitted, typeof query === "string" ? query : "");
},
{ preventDefault: true },
);
return html`<form onsubmit=${submit}>
<label>Search terms <input name="query" type="search" required /></label>
<button type="submit">Use query</button>
<output>Submitted query: ${submitted}</output>
</form>`;
});
currentTarget is the element registered for this handler: the form. target is where the event
originated and can be a nested element. Read the needed browser data at this boundary, then pass
ordinary values to application work. Here the output changes only when the form is submitted;
typing alone edits the browser’s input buffer.
Decide native cancellation before awaiting application work
The form in this example is handled by the application, so its handler records preventDefault
as a pre-handler option. Cancellation must happen during dispatch, before asynchronous work begins.
EventHandler.make accepts native AddEventListenerOptions plus preventDefault,
stopPropagation, and stopImmediatePropagation. Native capture, passive, and signal keep
their meaning. A passive listener cannot cancel the default action, and a noncancelable event
cannot be canceled; these follow the browser’s
preventDefault contract.
Use once only when the registration should handle one matching event. It is not a policy for
preventing overlapping requests; those belong in the operation’s state.
When the handler can fail
The local update above needs no service and has no expected failure. If it is replaced by a request,
the handler’s error and required-service channels remain part of the template’s type. Provide the
service at the application boundary and recover expected failures where the page can show feedback.
EventHandler.catchCause can recover a
reusable handler. A try/catch around html cannot catch a failure from a later submission.
Check the submitted value
Enter a query and submit the form. The output should receive that query and the page should not
navigate. In a DOM test, dispatch a cancelable SubmitEvent and check both the output and
event.defaultPrevented. Testing Typed systems covers mounting
and teardown checks.
For delegation mechanics and listener options across mounts, see EventSource delegation. Continue with keyed collections to keep item identity through list changes.