Watching Values
Apply this skill when one part of the form needs to react to another field’s value — show/hide a section based on a checkbox, compute a total, mirror a value into a preview, etc. v4 added useWatch, backed by the engine’s form-level change event (emitted on every control edit).
Three call shapes
useWatch(); // -> Record<string, any> — the whole collected values objectuseWatch("user.email"); // -> any — that single control's valueuseWatch(["city", "zip"]); // -> any[] — those controls' values, in orderEach form returns the current value(s) and re-renders the calling component whenever any control changes (or the form resets). Must be used inside a <Form> / <NativeForm> to be reactive (it reads useForm()).
How reactivity works
useWatch subscribes to two form events:
change— the form-level broadcast the engine fires on every control value change.reset— so watchers re-read afterform.reset().
On either, it forces a re-render and re-reads via form.values() (whole form), form.value(name) (single), or a map of form.value(name) (array). It re-renders on any change, not just the watched field — fine for most UIs; if you watch one field in a hot form and want to minimize renders, prefer a raw subscription (below).
Pattern — conditional field
import { useWatch } from "@mongez/react-form";
function ShippingSection() { const sameAsBilling = useWatch("sameAsBilling");
if (sameAsBilling) return null;
return ( <> <TextInput name="shipping.line1" required /> <TextInput name="shipping.city" required /> </> );}sameAsBilling is the checkbox control’s value; toggling it re-renders ShippingSection and shows/hides the inputs. (Remember: a hidden/unmounted control is unregistered, so its value won’t be collected — mount conditionally only when you want the field dropped from values.)
Pattern — computed summary (multiple names)
function PriceSummary() { const [qty, unitPrice] = useWatch(["qty", "unitPrice"]); const total = (Number(qty) || 0) * (Number(unitPrice) || 0);
return <p>Total: {total.toFixed(2)}</p>;}Pattern — live preview (whole form)
function LivePreview() { const values = useWatch(); return <pre>{JSON.stringify(values, null, 2)}</pre>;}useWatch() returns the dot-notation-nested object (same shape onSubmit receives), recomputed on every change.
Behavior outside a form
If there’s no surrounding form, useWatch returns safe defaults instead of throwing:
useWatch()→{}useWatch(name)→undefineduseWatch(names)→ an array ofundefined, one per name
This keeps a shared component that might be used outside a form from crashing — but it won’t be reactive there.
useWatch vs a raw change subscription
| Need | Use |
|---|---|
| read a value and re-render UI from it | useWatch |
| run a side effect on change (analytics, autosave, imperative DOM) | form.on("change", cb) — see the form-events skill |
| minimize re-renders while watching one field in a frequently-changing form | raw change subscription with your own equality check |
useWatch always re-renders on any change; a raw subscription lets you decide what to do (and whether to set state) per event.
Not the same as useInputValue
useInputValue(initialValue) (also exported) is just a small local-state helper — useState plus an event-or-value unwrapper for an input’s onChange. It is not a form-control reader: it doesn’t take a control name and isn’t tied to the form. For reading registered controls reactively, use useWatch.
Anti-patterns
- Calling
useWatchto drive validation — watching is for rendering/derived UI; cross-field validation belongs in a rule (matchRule, or a customInputRulereadingformin itsvalidate). See the validation-rules and recipes skills. - Expecting reactivity outside a form — outside
<Form>it returns static defaults; mount the watcher inside the form. - Watching a heavy form with
useWatch()(whole object) when you only need one field — preferuseWatch(name)(or a raw subscription) to avoid re-serializing the whole form on every keystroke.