# useComputedFields

URL: https://docs.forms.saastro.io/docs/use-computed-fields
> Reactively derives field values from other fields — recomputes whenever a dependency changes.

# 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`](/docs/use-form-state) (and therefore inside `<Form />`). Call it yourself only when building a custom form renderer with your own `react-hook-form` instance.

---

## Signature

```tsx

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:

```ts
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 `computed` is active** — its value is owned by the computation, not by user input.
- `compute` receives **all** current form values, but recomputation is only triggered by changes to the fields listed in `dependsOn`. If `compute` reads a field, list it in `dependsOn`.
- **Not JSON-serializable.** `compute` is 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](/docs/hidden-fields) with their serializable resolvers.
- **No `FieldBuilder` method.** `FieldBuilder` has no `.computed()` — set the prop on a raw field config. With `FormBuilder`, merge raw configs via `.addFields()` (see the example below).

---

## How It Works

1. Collects all fields whose config has `computed` — if there are none, it does nothing
2. On mount, runs an initial computation for every computed field (so `compute` must tolerate default/empty values)
3. Subscribes to form changes via `methods.watch()`; when the changed field is in a `dependsOn` list, recomputes the affected fields from `methods.getValues()`
4. Writes each result with `setValue(name, value, { shouldDirty: false })` — computed updates never mark the form dirty
5. Skips a dependency-triggered write when the new value is strictly equal (`===`) to the current one — note that a `compute` returning a fresh object or array every time will always write. The mount-time computation (step 2) always writes, so it overrides any `defaultValue` on the computed field
6. 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()`:

```tsx

// 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(...)` inside `compute` (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:

```tsx

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](/docs/conditional-logic) — Show, hide, or disable fields based on other values (vs. computing a value)
- [Hidden Fields](/docs/hidden-fields) — Values resolved once at mount via JSON-serializable resolvers
- [useFormState](/docs/use-form-state) — Runs this hook automatically as part of the form engine
- [FormBuilder](/docs/formbuilder) — `addFields()` for raw configs, and what keeps a config JSON-serializable
