Data and forms
Fetch with @lit-material/http, cache and refresh with TaskController, validate with FormController: three small pieces that fit together without a framework.
An HTTP client
createHttp wraps fetch: a base URL, JSON in and out, query objects, timeouts, and an HttpError with the status and parsed body for anything outside 200–299. Interceptors add headers and can refresh a token and retry once on 401.
import { createHttp } from "@lit-material/http";
export const api = createHttp({ baseUrl: "/api", headers: () => ({ Authorization: `Bearer ${session.token}` }) });
api.use({
response: async (response, { retry, attempt }) =>
response.status === 401 && attempt === 1 ? (await session.refresh(), retry()) : response,
});>Cached data
Give a task a key and its result is shared: every component asking for the same key shows it at once and shares one request, a stale value shows while a fresh one loads, and retry handles flaky networks.
private readonly user = new TaskController(this, {
task: ([id], signal) => api.get(`/users/${id}`, { signal }),
args: () => [this.userId] as const,
key: ([id]) => ["user", id],
staleTime: 30_000,
retry: 2,
refetchOnFocus: true,
});
After a change, taskCache.invalidate(["user"]) re-fetches every user on screen, and taskCache.set(key, value) shows a value everywhere straight away.
Validated forms
FormController reads a form's values (dotted names nest: items.0.name), runs field validators, sync or async, and any Standard Schema (zod, valibot, arktype), and shows the messages on the fields themselves. handleSubmit runs everything and calls you only with valid values.
private readonly form = new FormController(this, {
form: () => this.formEl,
schema: signupSchema, // a zod schema, say
validators: {
email: async (value, _values, signal) =>
(await api.get("/emails/taken", { query: { value }, signal })) ? "Already registered" : undefined,
},
});
render() {
return html`<form @submit=${this.form.handleSubmit((values) => api.post("/signup", values))}>…</form>`;
}
Repeating rows use a FieldArrayController: stable keys for repeat(), and append, remove and move.