useGenericFieldState
Holds a single form field's value together with its validation error message. It returns a MutableGenericFieldState<T> - a mutable value you read and write through .value, plus an errorMessage and a validate() method.
class CommentBox extends HookWidget {
const CommentBox({super.key});
Widget build(BuildContext context) {
// useFieldState is the String specialization - initialValue defaults to "".
final comment = useFieldState();
return Column(
children: [
TextField(onChanged: (it) => comment.value = it), // <- write the value
Text('${comment.value.length} characters'), // <- read it back, rebuilds on change
],
);
}
}
Signature
MutableGenericFieldState<T> useGenericFieldState<T>({required T initialValue});
// String specialization, documented separately:
MutableFieldState useFieldState({String? initialValue}); // initialValue defaults to ""
MutableGenericFieldState<T> implements MutableValue<T> and Validatable<T>:
value- read and write the current field value; writing triggers a rebuild.errorMessage- aValidatorResult?(aString Function(BuildContext)?), so the message is resolved at render time and the state hook stays free ofBuildContext. Read it to show the error, or assign it to set one.validate(validator)- runs the validator against the current value, stores the result inerrorMessage, and returns whether the field is valid (truewhen there is no error).
useFieldState is the String specialization - the same state with T fixed to String and initialValue defaulting to "". It is documented on its own page in the Flutter category; reach for it for text fields and use useGenericFieldState<T> when the field holds something else.
Use cases
-
Any form field. The value drives the input, the error message drives the validation display, and
validate()plugs into a submit'sshouldSubmit. -
Non-string fields - a dropdown, a date picker, a toggle - where the field still wants a value plus a validation slot:
enum Priority { low, medium, high }class PrioritySelector extends HookWidget {const PrioritySelector({super.key});Widget build(BuildContext context) {// useGenericFieldState carries any type, not just String.final priority = useGenericFieldState<Priority>(initialValue: Priority.medium);return DropdownButton<Priority>(value: priority.value,onChanged: (it) => priority.value = it ?? Priority.medium,items: [for (final it in Priority.values) DropdownMenuItem(value: it, child: Text(it.name)),],);}} -
Validation as part of a submit. Run every field in
shouldSubmitand abort if any fails - the fields already carry their messages, so no separateisFormValid()helper is needed:bool validate() => [emailState.validate((it) => isValidEmail(it) ? null : (context) => 'Invalid email'),passwordState.validate((it) => it.length >= 8 ? null : (context) => 'Min 8 characters'),].every((it) => it);void login() => submitState.runSimple<void, Never>(shouldSubmit: () => validate(), // <- the one gate; fields keep their error messagessubmit: () => auth.logIn(emailState.value, passwordState.value),);
This field state pairs with the TextEditingControllerWrapper widget, which bridges a MutableFieldState to a Flutter TextField's controller so the two stay in sync without manual onChanged wiring.
Caveats
-
errorMessageis a function, not aString. It is aValidatorResult?(String Function(BuildContext)?) so localized messages resolve against aBuildContextat render time. Build it as a closure, not a bare string:// DON'T - a String is not a ValidatorResultemailState.errorMessage = 'Invalid email';// DO - a closure resolved at render timeemailState.errorMessage = (context) => 'Invalid email'; -
Validate in the submit's
shouldSubmit, not in a separate pre-check.validate()already setserrorMessageas a side effect, so a manualif (!valid) returnbefore the submit duplicates the gate. -
Clear stale server-side errors in
beforeSubmit. A field error set from a previous failed API call lingers inerrorMessageuntil you reset it (field.errorMessage = null) orvalidate()overwrites it. -
The value and the error are independent. Changing
.valuedoes not clearerrorMessage- re-runvalidate()(or clear the message) if you want the error to disappear as the user edits.
See also
- useFieldState - the
Stringspecialization for text fields - useSubmitState -
validate()slots into itsshouldSubmitgate - useState - the plain mutable value behind a field that needs no validation