Drive a form from a machine
A wizard built on a form library already has a source of truth: the form. It does not want the machine to hold its
data, only to answer "given these values at this step, may I fire this, and where does it land?"
createFormView, from @traxsharp/state-machine, gives that answer from the machine's
generated twin, with no server and nothing stored. A machine a server holds is
driven with useServerMachine instead.
Map the steps and project the values
Give it the two things only the page knows: which UI step means which machine state, and how the form's values become the machine's context.
import { createFormView } from '@traxsharp/state-machine';
import { checkout } from './trax/checkout/checkout.machine.g';
const wizard = createFormView(checkout, {
steps: { cart: 'Cart', review: 'Review', receipt: 'Paid' },
toContext: (values) => ({
items: values.cart.items,
total: values.cart.total,
currency: 'USD',
receipt: null,
}),
});steps is given in one direction only. The inverse, a state's step, is derived, so the two cannot drift, and a map
that sends two steps to one state is refused when the view is built. toContext may be called without its second
argument, so give it a default when you use one.
Ask the machine
wizard.can('cart', values, 'Next'); // false while the guard declines, such as an empty cart
wizard.advance('cart', values, 'Next'); // { ok: true, step: 'review' }, or { ok: false, reason, detail }
wizard.serialize('cart', values); // canonical JSON of the snapshot, for an autosave
wizard.resume(storedJson); // { step, snapshot } to reopen, or nulladvancefires the trigger on a snapshot built from the values and answers the step the machine moved to, including any branch the machine chose, or why it declined. Nothing is kept: the form still owns the values.cananswers the same question without the step, for enabling a control.serializewrites the snapshot the server would store, stamped with the machine's id and version, so an autosave can keep it.resumereads a stored snapshot and answers the step to reopen it at. It answers null for a snapshot that does not read, and for one in a committed state (.Committed()in C#): a paid checkout does not reopen as a draft.isCommitted(json)answers that alone.