Skip to content

avoid_unnecessary_hook_widgets

v0.1.0 Warning Fix Widget Best Practices

Flags a HookWidget whose build calls no hook, and a HookBuilder whose builder calls none.

HookWidget installs a hook-management layer over the normal widget lifecycle. With no hooks that layer is pure overhead, and the class advertises state management that is not there. The quick fix rewrites the superclass to StatelessWidget.

This rule is in the opinionated preset, so it is on with preset: opinionated and preset: pedantic. No configuration.

See also: flutter_hooks

The common way in is a refactor: the useState moved up to the parent and nobody changed the base class back.

class Greeting extends HookWidget {
const Greeting({required this.name, super.key});
final String name;
@override
Widget build(BuildContext context) {
return Text('Hello $name');
}
}
class Greeting extends StatelessWidget {
const Greeting({required this.name, super.key});
final String name;
@override
Widget build(BuildContext context) {
return Text('Hello $name');
}
}

A HookBuilder exists to open a hook scope for a small part of a tree. One whose body calls no hook is a Builder with extra machinery:

// Don't
HookBuilder(
builder: (context) => const Text('Static'),
);
// Do
Builder(
builder: (context) => const Text('Static'),
);

Only a class whose extends clause names HookWidget or HookConsumerWidget directly is checked. A subclass of your own AppHookWidget base is not.

Hook detection is by name — an identifier matching use followed by a capital or a digit, optionally prefixed with _. A helper of your own called useFormatting() that is not really a hook therefore keeps the widget quiet.

A HookConsumerWidget that uses neither hooks nor ref is reported here and by avoid_unnecessary_consumer_widgets. The two answer different questions and point at different nodes.

many_lints.yaml
rules:
avoid_unnecessary_hook_widgets: false

To keep the rule on but skip certain paths, use per-rule exclude.