Skip to content

Editing an existing record

A form that opens blank and is submitted once needs almost none of this. A form that loads a record, is saved, reloads what the server actually stored and carries on being edited needs all of it: rebasing onto new data so "unsaved changes" means something, checking one answer without judging the whole form, reordering rows without walking them past each other, and reading a single field's state in one go.

Live form
Purchase order

Lines

No lines yet.

State

Form      dirty=False  submits=0  validated=False  valid=True
Customer  touched=False  dirty=False  invalid=False  messages=0
          first error: —
Lines     0   total: €0.00   (loaded 0x)
View source
// --- Rebasing onto what the server actually stored -----------------------------------------
// Reset() puts back the values the form was *constructed* with, which after a save are the wrong
// ones. Reset(values) writes the new ones and makes them the baseline, so IsFormDirty, an "unsaved
// changes" prompt and an undo button all mean what they say again. React Hook Form spells it
// reset(values).
state.Reset(new Dictionary<string, object?>
{
    ["Customer"] = stored.Customer,
    ["Lines[0].Description"] = stored.Lines[0].Description,
    // ... every field the round-trip came back with
});

// --- Checking one answer, not the whole form -----------------------------------------------
// The path is enough; the schema resolves the definition. Returns false when nothing in the schema
// has that path, so a typo does not silently pass. React Hook Form spells it trigger(name).
await state.ValidateFieldAsync("Customer");

// --- Everything known about one field, in one read ------------------------------------------
var field = state.GetFieldState("Customer");
// field.IsTouched, field.IsDirty, field.IsInvalid, field.Error, field.Messages

// --- Saving a draft ---------------------------------------------------------------------------
// Snapshot() maps every path the schema binds to onto its value — including one entry per repeater
// row — and Reset(values) takes exactly that shape back. Together they are a complete
// save-and-restore: stash it in session storage while the user is halfway through, hand it back
// when they return.
var draft = state.Snapshot();
// … later …
state.Reset(draft);

// --- Repeater operations ---------------------------------------------------------------------
// Swap exchanges two rows and leaves every other row exactly where it was — which is what a
// drag-and-drop reorder means. Move shuffles everything in between along by one.
state.SwapArrayItems("Lines", 0, 1);

// Clear empties the list in one change, taking every row's messages and touched/dirty state with
// it, instead of costing a re-render and a re-index per row.
state.ClearArrayItems("Lines");

// --- One error at a time -----------------------------------------------------------------------
// A password that is too short, has no digit and has no symbol otherwise stacks three complaints
// under one box. Every rule still runs — this is about what the user reads, not what is checked.
state.SingleErrorPerField = true;

// --- Review mode ------------------------------------------------------------------------------
// A read-only wizard keeps Back and Next (reading a form is not editing it) and loses Submit and
// Reset.
state.ReadOnly = true;
An unhandled error has occurred. Reload 🗙