useComputedFields
Keeps computed field values in sync with the fields they depend on. Any field whose config has a computed property gets its value recalculated automatically — once on mount, and again whenever one of its dependencies changes.
You probably don’t need this directly. It runs automatically inside
useFormState(and therefore inside<Form />). Call it yourself only when building a custom form renderer with your ownreact-hook-forminstance.
Signature
import { useComputedFields } from '@saastro/forms';
useComputedFields(methods, fields);
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
methods | UseFormReturn<Record<string, unknown>> | Yes | react-hook-form instance from useForm() |
fields | Fields | Yes | Form field configurations from config.fields |
Return Value
This hook returns void. It operates as a side-effect only — writing computed values via methods.setValue().
The computed Field Prop
Any field can become computed by adding a computed config:
computed?: {
/** Field names this computation depends on */
dependsOn: string[];
/** Pure function that computes the value from all form values */
compute: (values: Record<string, unknown>) => unknown;
};
- The field is read-only while
computedis active — its value is owned by the computation, not by user input. computereceives all current form values, but recomputation is only triggered by changes to the fields listed independsOn. Ifcomputereads a field, list it independsOn.- Not JSON-serializable.
computeis a function, so a config containing computed fields cannot be stored as JSON. If you need a fully serializable config, derive the value server-side after submission, or — for values resolved once at mount — use hidden fields with their serializable resolvers. - No
FieldBuildermethod.FieldBuilderhas no.computed()— set the prop on a raw field config. WithFormBuilder, merge raw configs via.addFields()(see the example below).
How It Works
- Collects all fields whose config has
computed— if there are none, it does nothing - On mount, runs an initial computation for every computed field (so
computemust tolerate default/empty values) - Subscribes to form changes via
methods.watch(); when the changed field is in adependsOnlist, recomputes the affected fields frommethods.getValues() - Writes each result with
setValue(name, value, { shouldDirty: false })— computed updates never mark the form dirty - Skips a dependency-triggered write when the new value is strictly equal (
===) to the current one — note that acomputereturning a fresh object or array every time will always write. The mount-time computation (step 2) always writes, so it overrides anydefaultValueon the computed field - Unsubscribes on unmount
Computed values live in form state like any other field value: they pass through validation, transforms, and the submitted payload normally.
Example: Declaring a Computed Field
Since the hook runs automatically inside <Form />, the typical “usage” is just the field config. FieldBuilder has no computed() method, so pass the computed field as a raw config through addFields():
import { FormBuilder } from '@saastro/forms';
import type { Fields } from '@saastro/forms';
// Raw config — the only way to declare a computed field
const computedFields: Fields = {
total: {
type: 'text',
label: 'Total (€)',
computed: {
dependsOn: ['quantity', 'unitPrice'],
compute: (values) =>
String((Number(values.quantity) || 0) * (Number(values.unitPrice) || 0)),
},
},
};
const config = FormBuilder.create('order')
.addField('quantity', (f) => f.type('number').label('Quantity').required())
.addField('unitPrice', (f) => f.type('number').label('Unit price (€)').required())
.addFields(computedFields)
.addStep('main', ['quantity', 'unitPrice', 'total'])
.build();
Native number inputs produce string values, so coerce with
Number(...)insidecompute(with a fallback for empty values — the initial computation runs on mount, before the user types anything).
Example: Custom Form Renderer
When driving react-hook-form yourself instead of using <Form />, wire the hook manually:
import { useComputedFields } from '@saastro/forms';
import type { FormConfig } from '@saastro/forms';
import { useForm } from 'react-hook-form';
function CustomFormRenderer({ config }: { config: FormConfig }) {
const methods = useForm<Record<string, unknown>>({ defaultValues: {} });
// Keeps computed field values in sync with their dependencies
useComputedFields(methods, config.fields);
const onSubmit = (values: Record<string, unknown>) => {
console.log(values); // includes the computed values
};
return (
<form onSubmit={methods.handleSubmit(onSubmit)}>
{/* Render fields... computed fields update as dependencies change */}
</form>
);
}
No provider is required — the hook takes methods explicitly.
Related
- Conditional Logic — Show, hide, or disable fields based on other values (vs. computing a value)
- Hidden Fields — Values resolved once at mount via JSON-serializable resolvers
- useFormState — Runs this hook automatically as part of the form engine
- FormBuilder —
addFields()for raw configs, and what keeps a config JSON-serializable