Skip to content

Calculations

Derived fields that update when dependencies change — no manual onChange wiring.

Previous: Rules · Next: Formatters

Problem → solution

ProblemSolution
Totals recomputed in every onChangeform.calculate(path, compute)
Nested deps hard to trackExplicit deps in the options form

Overview

ts
const form = createForm({
  initialValues: { price: 100, quantity: 1, total: 0 },
});

form.calculate("total", ({ values }) => Number(values.price) * Number(values.quantity));

When price or quantity changes, total is recalculated and written via setValue.


Options form

ts
form.calculate("total", {
  deps: ["price", "quantity"],
  compute: ({ values }) => Number(values.price) * Number(values.quantity),
});

Use deps when the compute function reads nested paths that are hard to infer, or to limit which changes trigger recalculation.


Patterns

Line item total

ts
form.calculate("lineTotal", ({ values }) => Number(values.unitPrice) * Number(values.qty));

Conditional derived value

ts
form.calculate("discount", ({ values }) =>
  values.coupon === "SAVE10" ? Number(values.subtotal) * 0.1 : 0,
);

Keep derived fields read-only in UI

Treat calculated paths as display-only — bind them for output, but avoid editable inputs (or when().disable("total")).


Element structure

React JSX

tsx
<form {...form.form()}>
  <label>
    Price
    <input {...form.field("price")} type="number" />
  </label>
  <label>
    Quantity
    <input {...form.field("quantity")} type="number" />
  </label>
  <p>
    Total: <strong>{form.state.values.total}</strong>
  </p>
  {/* or a read-only input */}
  <input {...form.field("total")} readOnly aria-readonly="true" />
</form>

Native HTML

html
<form id="cart">
  <input name="price" type="number" />
  <input name="quantity" type="number" />
  <output name="total" for="price quantity"></output>
</form>

Keep total out of user edits — update the <output> (or a read-only input) from form.state.values.total in your render loop.


Cheat sheet

ts
form.calculate("path", ({ values }) => /* derived */);
form.calculate("path", { deps: ["a", "b"], compute: ({ values }) => … });
form.values("total");

Next: Formatters — phone, currency, slug, credit card.

JOC is a JayOnCode monorepo of @jayoncode/* packages. Docs sync from source via TypeDoc and sync scripts.