Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/faq.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ Two gates sit in front of it: the dependent field must be in a mode other than `
it at least once. Under `manual` the mismatch shows up on submit instead. Also check that the *value* of a watched
field changed — a status-only change never fires it. See [Cross-field logic](./validation/cross-field.mdx).

If the dependent field was prefilled and really has to react before anyone touches it, call `validate()` on it from a
listener on the watched field: `validate()` ignores both gates. Keep the rule in the validator; the listener only
decides when it runs.

</Accordion>
<Accordion title="The error disappears while I type and comes back a moment later.">

Expand Down
5 changes: 4 additions & 1 deletion docs/validation/cross-field.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,10 @@ class _PasswordFormState extends State<PasswordForm> {
- It re-runs **this** field's **sync** validator. It does not copy or derive values, and it does not re-run the async
validator — this field's own value did not change, so its verdict still stands.
- It obeys the [gate](./modes.mdx): nothing happens while this field is in `ValidationMode.manual`, and nothing on a
field the user has never edited. Under `manual` the mismatch shows up on submit instead.
field the user has never edited. Under `manual` the mismatch shows up on submit instead. When a prefilled field has to react
before anyone touches it — an instructor loaded from the server, an aircraft they may not fly — call `validate()` on
it from a listener on the watched field; `validate()` ignores the gate. Compare the watched value yourself, since a
listener also fires on status changes, and remember that `validate()` runs the async validator too.
- It fires when a watched **value** changes, never on a status-only change of the sibling, so it cannot loop.
- A second call replaces the previous subscription; the subscription is dropped on `dispose`.
- A code pushed with `setError` gives way to whatever the validator now returns, like any other re-run.
Expand Down
23 changes: 23 additions & 0 deletions skills/advanced_forms/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,29 @@ children.subscribeToFields([adults]);
only `adults` lights up live — `children` shows its half at the first `validate()`. Both
showing simultaneously before submit is not something the modes can give you; render the
pair's message once, from the form, if the design needs it.
- **When a prefilled field MUST react anyway** — the instructor came from the server, the user
switches the aircraft, and the instructor must show "not rated" although nobody touched that
field — `subscribeToFields` cannot do it (rule 2). Do not reach for `setError` to hand-write
the error next to the validator. Call `validate()` on the dependent field from a listener on
the watched field: `validate()` ignores the gate and the mode, so it reaches an untouched
field, and the sync result lands synchronously. Compare the value yourself, because a listener
also fires on status changes:

```dart
// In the form constructor, after registerFields. The rule itself stays in the
// instructor's validator; this only decides WHEN it runs.
var lastAircraft = aircraft.fieldValue;
aircraft.addListener(() {
if (aircraft.fieldValue == lastAircraft) return;
lastAircraft = aircraft.fieldValue;
instructor.validate(); // fire-and-forget; also runs the async validator, if any
});
```

Two traps: a `validate()` already in flight is shared, so a second call before it finishes
gets the first round's result — one value change per event-loop turn is fine, a synchronous
loop of writes is not; and `validate()` also runs `asyncValidation`, so a field with a server
check will hit the server on every change of the watched field.

**Value depends on another field** ("when B changes, set A" — totals, mirroring, clearing a
dependent selection). Use the form's `addRelation(source, select, onChange)`, in the
Expand Down
Loading