Calculated values
A field with calculateValue derives its value from an expression over other
fields. It updates whenever a dependency changes — no user interaction, no
useEffect in your code.
{ "name": "subtotal", "type": "number", "label": "Subtotal", "calculateValue": "%qty% * %price%"}Calculated fields are ordinary fields in every other respect: they appear in
form data, they’re included in the onSave payload, and they can carry
validation. Most of the time you’ll want them read-only in your component —
check field.calculateValue and render accordingly.
Chains settle in one write
Section titled “Chains settle in one write”The interesting case is a calculated field that depends on another calculated
field. parseSchema builds a dependency graph once, at parse time, and
updateData walks it — so a chain of any depth resolves inside a single data
write, not one render pass per link.
Change the quantity and every downstream field is already correct by the time
updateData returns. That matters beyond render counts: form data is
consistent the moment you write to it, whether or not the dependent fields
are mounted. A calculated total that lives inside a collapsed section still
holds the right number.
Evaluation order
Section titled “Evaluation order”The graph is topologically sorted, so a diamond evaluates correctly and exactly once:
subtotal ╱ ╲ discount taxable ╲ ╱ totaltotal waits for both inputs rather than recomputing once per branch.
Cycles
Section titled “Cycles”A cycle — a calculates from b, b calculates from a — is a schema
authoring error. It’s detected when the schema is parsed, warned about once,
and the fields involved are excluded from graph propagation:
[driven-form] Circular calculateValue dependency between: a, b.These fields are excluded from synchronous propagation.The warning is never debug-gated. Those fields silently stop auto-updating, which is exactly the kind of failure you want to hear about.
Limitation: repeater rows
Section titled “Limitation: repeater rows”The graph is built from the flat field map, and row templates (columnFields)
aren’t in it. Fields inside a repeater row keep the older effect-based
propagation — correct, but settling over successive passes rather than in one
write.
Form-level fields are fully in the graph, including ones that aggregate over
rows. So a per-row lineTotal uses the effect path, while a form-level
sum(%items%, 'lineTotal') settles synchronously. See
cross-row aggregates.
Expressions available
Section titled “Expressions available”Anything the expression language supports: arithmetic, ternaries, Number(),
Math.*, and any registered expression function.
{ "name": "premium", "type": "number", "label": "Premium", "calculateValue": "Number(%sumInsured%) * 0.005"}Number() is worth reaching for when a value might arrive as a string — input
elements hand back strings, and '10' * 2 is 20 but '10' + 2 is '102'.