Skip to content

Calculations

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

Previous: Rules · Next: Formatters

Import path

ts
import { createForm, calculate } from "@jayoncode/form-intelligence";
// or instance API: form.calculate(...)

Main entry only for typical use. Entrypoints.

The headless engine pass (runCalculations) lives on @jayoncode/form-intelligence/rules for tests/tooling that need to run calculations without a live form — see Rules → runCalculations. Prefer form.calculate() / calculate() shown below in app code.

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));

// Fluent (same registration)
form
  .calculate("total")
  .from("price", "quantity")
  .compute(({ values, get }) => Number(get("price")) * Number(values.quantity));

When price or quantity changes, total is recalculated. Circular derived fields throw ConfigurationError.


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.calculate("path").from("a", "b").markDirty().compute(({ get }) => …);
form.values("total");

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

JOC — headless @jayoncode/* TypeScript libraries. Docs sync from package source via TypeDoc and sync scripts.