Use when: a form has non-trivial cross-field validation rules, async field-level
checks (e.g. “username already taken”), or multi-step workflows that share state
across steps.
Don’t use when: a form is a simple uncontrolled HTML form or a library like
React Hook Form already owns it — adding a Cubit on top creates two sources of
truth.
Keep field values and a touched map in state. Derive validation errors as a
getter — they recompute on every read and can never go stale. Show errors only for
touched fields so the initial load is clean.
Cubit<S> is a StateContainer<S> with emit / patch exposed as
public mutation surface. Today it adds nothing structurally beyond
StateContainer — both are inherited from the underlying
StructuralContainer<S>. Kept as a real class (not a type alias) because
downstream code does instance instanceof Cubit checks (see A2 audit).
The class body is intentionally empty: a no-op emit override would
still go through applyState, and patch is inherited from
StructuralContainer (path-diffed, microtask-flushed). If a caller
needs the old "skip if no real change" patch semantics, they can wrap
patch themselves or call emit after a manual equality check.
Override of StructuralContainer.patch that routes through the
StateContainer concerns: disposed guard, dev-only emit-rate check,
_changedWhileHydrating flag, pending-change capture (so legacy
listeners and stateChanged system events see the merged prev/next),
and the registry-level stateChanged notification. We still call
super.patch so path-marking semantics (the whole point of patch) are
preserved.
Override of StructuralContainer.patch that routes through the
StateContainer concerns: disposed guard, dev-only emit-rate check,
_changedWhileHydrating flag, pending-change capture (so legacy
listeners and stateChanged system events see the merged prev/next),
and the registry-level stateChanged notification. We still call
super.patch so path-marking semantics (the whole point of patch) are
preserved.
Override of StructuralContainer.patch that routes through the
StateContainer concerns: disposed guard, dev-only emit-rate check,
_changedWhileHydrating flag, pending-change capture (so legacy
listeners and stateChanged system events see the merged prev/next),
and the registry-level stateChanged notification. We still call
super.patch so path-marking semantics (the whole point of patch) are
preserved.
patch({
confirmPassword?: string |undefined
confirmPassword });
/** Mark a field as interacted with so its error becomes visible. */
Override of StructuralContainer.patch that routes through the
StateContainer concerns: disposed guard, dev-only emit-rate check,
_changedWhileHydrating flag, pending-change capture (so legacy
listeners and stateChanged system events see the merged prev/next),
and the registry-level stateChanged notification. We still call
super.patch so path-marking semantics (the whole point of patch) are
preserved.
Returns true if searchString appears as a substring of the result of converting this
object to a String, at one or more positions that are
greater than or equal to position; otherwise, returns false.
@param ― searchString search string
@param ― position If position is undefined, 0 is assumed, so as to search all of the String.
Override of StructuralContainer.patch that routes through the
StateContainer concerns: disposed guard, dev-only emit-rate check,
_changedWhileHydrating flag, pending-change capture (so legacy
listeners and stateChanged system events see the merged prev/next),
and the registry-level stateChanged notification. We still call
super.patch so path-marking semantics (the whole point of patch) are
preserved.
Override of StructuralContainer.patch that routes through the
StateContainer concerns: disposed guard, dev-only emit-rate check,
_changedWhileHydrating flag, pending-change capture (so legacy
listeners and stateChanged system events see the merged prev/next),
and the registry-level stateChanged notification. We still call
super.patch so path-marking semantics (the whole point of patch) are
preserved.
Override of StructuralContainer.patch that routes through the
StateContainer concerns: disposed guard, dev-only emit-rate check,
_changedWhileHydrating flag, pending-change capture (so legacy
listeners and stateChanged system events see the merged prev/next),
and the registry-level stateChanged notification. We still call
super.patch so path-marking semantics (the whole point of patch) are
preserved.
Override of StructuralContainer.patch that routes through the
StateContainer concerns: disposed guard, dev-only emit-rate check,
_changedWhileHydrating flag, pending-change capture (so legacy
listeners and stateChanged system events see the merged prev/next),
and the registry-level stateChanged notification. We still call
super.patch so path-marking semantics (the whole point of patch) are
preserved.
For async checks (username availability, coupon validation), pattern them like a
debounced async action — see Debounce — and merge the
result into the errors object or a separate asyncErrors field.