HookCoordinator
Runs a use callback as the body of a HookWidget and passes its result to a builder. It is the most general wrapper in this category: it lets you call hooks inline, anywhere in a widget tree, without declaring a dedicated HookWidget subclass.
class CounterCard extends StatelessWidget {
const CounterCard({super.key});
Widget build(BuildContext context) {
// No dedicated HookWidget subclass: use() runs the hooks, builder consumes them.
return HookCoordinator(
use: () => useState(0),
builder: (count) => TextButton(
onPressed: () => count.value++,
child: Text('Tapped ${count.value} times'),
),
);
}
}
Constructor
HookCoordinator<T>({
Key? key,
required T Function() use,
required Widget Function(T) builder,
});
use- a callback run inside the widget'sbuild, so it may call any hooks. Whatever it returns becomesT.builder- receives the value returned byuseand produces the widget.
The implementation is exactly builder(use()) inside a HookWidget.build. Because use runs in a hook build, the rules of hooks apply: call hooks unconditionally and in a stable order across rebuilds.
Use cases
-
Using a hook for one small piece of a tree without splitting out a
HookWidget- a singleuseState-backed toggle, a loneuseAnimationControllerdriving one widget. -
Returning a record (or any object) from
useto expose several hooks to the builder at once:class Greeting extends StatelessWidget {const Greeting({super.key, required this.name});final String name;Widget build(BuildContext context) {// Return a record from use() to expose several hooks at once.return HookCoordinator(use: () {final visits = useState(0);final greeting = useMemoized(() => 'Hello, $name', [name]);return (visits: visits, greeting: greeting);},builder: (state) => TextButton(onPressed: () => state.visits.value++,child: Text('${state.greeting} (${state.visits.value})'),),);}}
Every other widget in this category is a specialization of this idea: each owns a specific hook or controller and exposes it through a builder. Reach for a dedicated wrapper when one fits - ComputedStateWrapper for a computed state, TextEditingControllerWrapper for a text field - and use HookCoordinator for the cases that don't have one.
Caveats
-
It is an escape hatch, not the default. For a screen, follow the Screen / State / View pattern - a
HookWidgetwhose state lives in ause<Feature>Statehook - rather than inlining logic through aHookCoordinator. Overusing it scatters state-building into the widget tree and undercuts the structure the pattern gives you. -
Keep
useto hook calls and cheap wiring. It runs on every rebuild, so heavy work belongs behinduseMemoized/useAutoComputedStateinside it, not in the body directly - exactly as in a normal hook build.
See also
- Basics - the rules of hooks and the Screen / State / View pattern this is an exception to
- ComputedStateWrapper - a specialization that owns a computed state
- PageControllerWrapper - a specialization that owns a
PageController