## Summary - Add comprehensive AI-oriented documentation for effect-view components, state, async rendering, queries, mutations, forms, streams, and runtime setup. - Fix `Mutation.mutate` so each invocation consistently uses its own key instead of reusing the previous mutation key. - Improve Vite Fast Refresh instrumentation for: - User-defined component wrappers. - Data-first and pipeline-based `withContext`/`withRuntime` entrypoints. - Nested function handling. - Stable hook signatures that preserve state for non-hook-related edits. - Add regression tests for mutation keys and refresh behavior. - Bump `effect-view` to `0.1.5` and `@effect-view/vite-plugin` to `0.0.2`. ## Testing - `bun run --cwd packages/effect-view test -- src/Mutation.test.ts` - 4 tests passed - `bun run --cwd packages/vite-plugin test -- src/plugin.test.ts` - 11 tests passed The full test suite was not run. --------- Co-authored-by: Julien Valverdé <julien.valverde@mailo.com> Reviewed-on: #76
3.2 KiB
3.2 KiB
MutationForm
A root form (implements Form.Form, see Form.md) that owns a local encoded draft and passes the valid decoded value to a Mutation when submit runs. Use for registration, checkout, search — any workflow with an explicit submit action.
import { Effect } from "effect"
import { Component, MutationForm, View } from "effect-view"
const form = yield* Component.useOnMount(() =>
MutationForm.make({
schema: ProfileSchema,
initialEncodedValue: { displayName: "", age: "", contact: { email: "" } },
f: ([profile, form]) => Effect.log(`Creating ${profile.displayName}, age ${profile.age}`),
}).pipe(MutationForm.thenRun),
)
const [canCommit, isCommitting] = yield* View.useAll([form.canCommit, form.isCommitting])
const runPromise = yield* Component.useRunPromise()
<button disabled={!canCommit || isCommitting} onClick={() => void runPromise(form.submit)}>
{isCommitting ? "Creating..." : "Create profile"}
</button>
MutationForm.make({ schema, initialEncodedValue, f })constructs the form; the underlyingMutation's input key is the tuple[decodedValue, form], not just the decoded value. Mostfimplementations only destructure the first element (profile.ageis a number even though the input edited a string);formis included sofcan, if needed, read other form state (e.g.form.issues,form.encodedValue) while handling the submission.MutationForm.thenRunstarts initial validation in the current scope.- Create once and keep stable — the usual home is
Component.useOnMount. form.submitruns the mutation only when the form can currently commit; schema issues block submission before the mutation ever runs.- Focus into subforms/fields with
Form.focusObjectOn/focusArrayAt/etc. (seeForm.md) and bind them to inputs withForm.useInput. - If
ffails with aSchema.SchemaError,form.submitformats that error intoform.issuesautomatically — the same formatting path used for client-side decoding errors — instead of only surfacing it as a mutation failure. Any other failure fromfis left as the mutation'sFailureand does not touchform.issues.
Example: schema-owned date conversion
class DateTimeUtcFromZoned extends Schema.transformOrFail(Schema.DateTimeZonedFromSelf, Schema.DateTimeUtcFromSelf, {
strict: true,
decode: input => ParseResult.succeed(DateTime.toUtc(input)),
encode: DateTime.setZoneCurrent,
}) {}
export class DateTimeUtcFromZonedInput extends Schema.transformOrFail(Schema.String, DateTimeUtcFromZoned, {
strict: true,
decode: (input, _options, ast) => Effect.flatMap(DateTime.CurrentTimeZone, timeZone =>
Option.match(DateTime.makeZoned(input, { timeZone, adjustForTimeZone: true }), {
onSome: ParseResult.succeed,
onNone: () => ParseResult.fail(new ParseResult.Type(ast, input, "Enter a valid date and time")),
})),
encode: value => ParseResult.succeed(DateTime.formatIsoZoned(value).slice(0, 16)),
}) {}
An <input type="datetime-local"> edits "2026-07-22T14:30"; the schema decodes it to a UTC DateTime.Utc (decode(...).pipe requires DateTime.CurrentTimeZone — provide DateTime.layerCurrentZoneLocal in the runtime) and the mutation receives the UTC instant directly. No manual date parsing in the component.