Skip to content

avoid_misused_hooks

v0.8.0WarningConfigurableHook Rules

This rule flags a useX() call inside a for, for-in, while, or do-while loop, including the for element inside a collection literal.

Hook state is addressed by call position. A hook inside a loop runs as many times as the loop iterates, so the number of hook calls depends on your data. The moment that data changes length, every hook after the loop shifts to a different slot and starts reading state that belongs to another hook.

The symptom is state that appears to jump between unrelated widgets, or a useEffect firing with the wrong dependencies — bugs that are hard to trace back to the loop.

This is the loop half of the rules of hooks; avoid_conditional_hooks covers the branching half.

See also: flutter_hooks rules

class MyWidget extends HookWidget {
const MyWidget(this.items);
final List<String> items;
@override
Widget build(BuildContext context) {
// The hook count changes with items.length
for (final item in items) {
final controller = useTextEditingController(text: item);
}
return const SizedBox();
}
}
class MyWidget extends HookWidget {
const MyWidget(this.items);
final List<String> items;
@override
Widget build(BuildContext context) {
// One hook call, regardless of how many items there are
final controllers = useMemoized(
() => items.map((i) => TextEditingController(text: i)).toList(),
[items],
);
return const SizedBox();
}
}

If each item genuinely needs its own hook state, give each one its own hook widget and let the framework keep the contexts separate.

Only loops are detected. A hook placed after an early return is skipped on some builds and shifts positions the same way, but proving that requires flow analysis and is out of scope here.

This rule is in the recommended preset, so it is on with preset: recommended or preset: opinionated. Add it to preset: core with avoid_misused_hooks: true.

To turn it off:

many_lints.yaml
rules:
avoid_misused_hooks: false

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

analysis_options.yaml
many_lints:
rules:
avoid_misused_hooks:
ignored_names: [useLocalHelper]
ignored_widgets: [GeneratedGrid]
Option Type Default Description
ignored_names list of strings [] Hook names never reported, for useX() helpers that only follow the naming convention
ignored_widgets list of strings [] Widget class names whose hooks are never reported