avoid-unnecessary-hook-widgets
- Group
- Performance
- Path
lint/performance/avoid-unnecessary-hook-widgets- Default
- Off, opt in
- Origin
- Falcon
- Domain
- flutter_hooks
- Rule
- 164 of 512
Flags confirmed hook widgets and HookBuilder callbacks that do not use hooks.
Replace an unused hook widget with a regular widget to simplify its lifecycle.
Only concrete, directly package-derived HookWidget and HookConsumerWidget
classes with their own build body, and inline HookBuilder callbacks, are
checked. Every call except a generative constructor of a confirmed Flutter
Widget subtype retains its immediate hook owner. Getter reads and cascades
also retain that owner; nested callbacks do not retain an outer owner.
Abstract classes, inherited builds, and unresolved hook widget identities are
skipped. Classes extended elsewhere in the same library are skipped; subclasses
in other libraries are not visible and must be checked before conversion.
Indirect hooks through overloaded operators or setters are not tracked.
Invalid
import 'package:flutter/widgets.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_hooks/flutter_hooks.dart' as hooks;
import 'package:hooks_riverpod/hooks_riverpod.dart';
class EmptyBlock extends HookWidget {
@override
Widget build(BuildContext context) { return const SizedBox(); }
}
class EmptyArrow extends hooks.HookWidget {
@override
Widget build(BuildContext context) => const SizedBox();
}
class EmptyConsumer extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) => const SizedBox();
}
final emptyArrow = HookBuilder(builder: (context) => const SizedBox());
final emptyBlock = hooks.HookBuilder(builder: (context) { return const SizedBox(); });
final explicitNew = new HookBuilder(builder: (context) => const SizedBox());
class OuterWithoutHooks extends HookWidget {
@override
Widget build(BuildContext context) => HookBuilder(builder: (context) {
useState(0);
return const SizedBox();
});
}
class CallbackOnly extends HookWidget {
@override
Widget build(BuildContext context) => Clickable(onPressed: () { useState(0); });
}
final nestedCallback = hooks.HookBuilder(builder: (context) {
return Clickable(onPressed: () { hooks.useState(0); });
});
final prefixedNew = new hooks.HookBuilder(builder: (context) => const SizedBox());
final parenthesized = (hooks.HookBuilder(builder: (context) => const SizedBox()));
class Clickable extends StatelessWidget {
const Clickable({required this.onPressed});
final void Function() onPressed;
@override
Widget build(BuildContext context) => const SizedBox();
}
class PlainFieldRead extends HookWidget {
const PlainFieldRead(this.child);
final Widget child;
@override
Widget build(BuildContext context) => child;
} class EmptyBlock extends HookWidget { class EmptyArrow extends hooks.HookWidget { class EmptyConsumer extends HookConsumerWidget { final emptyArrow = HookBuilder(builder: (context) => const SizedBox());final emptyArrow = HookBuilder(builder: (context) => const SizedBox());final emptyBlock = hooks.HookBuilder(builder: (context) { return const SizedBox(); });final emptyBlock = hooks.HookBuilder(builder: (context) { return const SizedBox(); });final explicitNew = new HookBuilder(builder: (context) => const SizedBox()); class OuterWithoutHooks extends HookWidget { class CallbackOnly extends HookWidget { final nestedCallback = hooks.HookBuilder(builder: (context) { final prefixedNew = new hooks.HookBuilder(builder: (context) => const SizedBox());final prefixedNew = new hooks.HookBuilder(builder: (context) => const SizedBox());final parenthesized = (hooks.HookBuilder(builder: (context) => const SizedBox())); class PlainFieldRead extends HookWidget {Valid
import 'package:flutter/widgets.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_hooks/flutter_hooks.dart' as hooks;
import 'package:hooks_riverpod/hooks_riverpod.dart';
class StateUser extends HookWidget {
@override
Widget build(BuildContext context) {
useState(0);
return const SizedBox();
}
}
class ConsumerUser extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
hooks.useEffect(() {});
return const SizedBox();
}
}
Widget useContent() { useState(0); return const SizedBox(); }
Widget _useContent() => useContent();
class CustomHookUser extends HookWidget {
@override
Widget build(BuildContext context) => useContent();
}
class PrivateHookUser extends HookWidget {
@override
Widget build(BuildContext context) => _useContent();
}
Widget indirectlyUsesHooks() { useState(0); return const SizedBox(); }
class IndirectUser extends HookWidget {
@override
Widget build(BuildContext context) => indirectlyUsesHooks();
}
abstract class AbstractWidget extends HookWidget {
@override
Widget build(BuildContext context) => const SizedBox();
}
class InheritedBuild extends StateUser {}
final withHook = HookBuilder(builder: (context) {
useState(0);
return const SizedBox();
});
final withCustomHook = hooks.HookBuilder(builder: (context) => useContent());
final withIndirectHook = HookBuilder(builder: indirectlyUsesHooksWithContext);
Widget indirectlyUsesHooksWithContext(BuildContext context) => indirectlyUsesHooks();
class MethodHookUser extends HookWidget {
Widget content() { useState(0); return const SizedBox(); }
@override
Widget build(BuildContext context) => content();
}
final prefixedHook = hooks.HookBuilder(builder: (context) {
hooks.useState(0);
return const SizedBox();
});
final prefixedNewHook = new hooks.HookBuilder(builder: (context) {
hooks.useEffect(() {});
return const SizedBox();
});
class GeneratedHooks extends HookWidget {
@override
Widget build(BuildContext context) {
final states = List.generate(3, (_) => useState(0));
return const SizedBox();
}
}
class HookBundle {
final state = useState(0);
HookBundle();
factory HookBundle.create() {
useState(0);
return HookBundle();
}
}
class FactoryHookUser extends HookWidget {
@override
Widget build(BuildContext context) {
new HookBundle.create();
return const SizedBox();
}
}
class FieldInitializerHookUser extends HookWidget {
@override
Widget build(BuildContext context) {
HookBundle();
return const SizedBox();
}
}
Widget get hookContent { useState(0); return const SizedBox(); }
class TopLevelGetterUser extends HookWidget {
@override
Widget build(BuildContext context) => hookContent;
}
class MemberGetterUser extends HookWidget {
Widget get content { useState(0); return const SizedBox(); }
@override
Widget build(BuildContext context) => content;
}
class ExplicitGetterUser extends HookWidget {
Widget get content { useState(0); return const SizedBox(); }
@override
Widget build(BuildContext context) => this.content;
}
class HookActions extends StatelessWidget {
void init() { useState(0); }
@override
Widget build(BuildContext context) => const SizedBox();
}
class CascadeHookUser extends HookWidget {
@override
Widget build(BuildContext context) => HookActions()..init();
}
class UnknownCallUser extends HookWidget {
@override
Widget build(BuildContext context) {
Unknown();
return const SizedBox();
}
}
class ExplicitUnknownCallUser extends HookWidget {
@override
Widget build(BuildContext context) {
new Unknown();
return const SizedBox();
}
}
class HookBase extends HookWidget {
@override
Widget build(BuildContext context) => const SizedBox();
}
class HookChild extends HookBase {
@override
Widget build(BuildContext context) {
useState(0);
return super.build(context);
}
}
class StaticHooks {
static Widget content() { useState(0); return const SizedBox(); }
}
class StaticHookUser extends HookWidget {
@override
Widget build(BuildContext context) => StaticHooks.content();
}
extension ContextHooks on BuildContext {
Widget hookContent() { useState(0); return const SizedBox(); }
}
class ExtensionHookUser extends HookWidget {
@override
Widget build(BuildContext context) => context.hookContent();
}
class Panel extends StatelessWidget {
const Panel();
static Widget withCounter() {
useState(0);
return const Panel();
}
factory Panel.counted() {
useState(0);
return const Panel();
}
@override
Widget build(BuildContext context) => const SizedBox();
}
class StaticWidgetMethodUser extends HookWidget {
@override
Widget build(BuildContext context) => Panel.withCounter();
}
class WidgetFactoryUser extends HookWidget {
@override
Widget build(BuildContext context) => Panel.counted();
}
class ExplicitWidgetFactoryUser extends HookWidget {
@override
Widget build(BuildContext context) => new Panel.counted();
}
class ShadowedWidgetConstructor extends HookWidget {
Widget SizedBox() {
useState(0);
return const Panel();
}
@override
Widget build(BuildContext context) => SizedBox();
}Configure avoid-unnecessary-hook-widgets
Set its severity in falcon.json. See the configuration guide.
{
"linter": {
"rules": {
"performance": {
"avoid-unnecessary-hook-widgets": "error"
}
}
}
}Or silence one occurrence with a suppression comment; the reason is required.
// falcon-ignore lint/performance/avoid-unnecessary-hook-widgets: <reason>More performance rules
- move-variable-closer-to-its-usage
Move final/const scalar literals into their only branch, or after an if.
- move-variable-outside-iteration
Reports loop-local final variables whose computation is independent of the loop.
- noop-primitive-operations
noop_primitive_operationsrecommendedFlags primitive operations that return their receiver unchanged.
- prefer-const-border-radius
prefer-const-border-radiusrecommendedflutterFlags
BorderRadius.only(...)whose four corner radii are all equal. - prefer-contains
prefer_containsrecommendedPrefers
containsto comparing the result ofindexOfwith a constant. - prefer-correct-edge-insets-constructor
prefer-correct-edge-insets-constructorrecommendedflutterFlags an
EdgeInsets.only(...)that a more specific constructor expresses better. - prefer-declaring-const-constructor
prefer_declaring_const_constructorflutterFlags a generative constructor that is legally eligible to be
const. - prefer-foreach
prefer_foreachPrefers
forEachto afor-in loop that only passes each element to a function.