Skip to content

Files

Latest commit

f8a87a3 Β· May 24, 2025

History

History
355 lines (278 loc) Β· 9.53 KB

README.md

File metadata and controls

355 lines (278 loc) Β· 9.53 KB

flutter_hooks_lintπŸͺπŸ΄β€β˜ οΈ

flutter_hooks_lint package very_good_analysis Build Status

A lint package providing guidelines for using flutter_hooks in your Flutter widget! 🦜

  • You can keep code that follows the rules outlined in the official documentation of flutter_hooks. βš“
  • A lint rules are available to improve both performance and readability. ✨
  • A lint rules supporting hooks_riverpod has been prepared. πŸ§‘β€πŸ€β€πŸ§‘

The currently available lint rules are as follows:

LintRule Description Quickfix
hooks_avoid_nesting You should use Hooks only inside the build method of a Widget.
hooks_avoid_within_class Hooks must not be defined within the class.
hooks_name_convention DO always prefix your hooks with use, https://pub.dev/packages/flutter_hooks#rules. βœ…
hooks_extends Using Hooks inside a Widget other than HookWidget or HookConsumerWidget will result in an error at runtime. βœ…
hooks_unuse_widget If you are not using Hooks inside of a Widget, you do not need HookWidget or HookConsumerWidget. βœ…
hooks_memoized_consideration Considering performance and functionality, there may be places where it is worth considering the use of useMemoized. βœ…
hooks_callback_consideration There are cases where you can use useCallback, which is the syntax sugar for useMemoized. βœ…

screencast

Installation

Add both flutter_hooks_lint and custom_lint to your pubspec.yaml:

dev_dependencies:
  custom_lint:
  flutter_hooks_lint:

Enable custom_lint's plugin in your analysis_options.yaml:

analyzer:
  plugins:
    - custom_lint

Enabling/disabling lints

By default when installing flutter_hooks_lint, most of the lints will be enabled.

You may dislike one of the various lint rules offered by flutter_hooks_lint. In that event, you can explicitly disable this lint rule for your project by modifying the analysis_options.yaml

analyzer:
  plugins:
    - custom_lint

custom_lint:
  rules:
    # Explicitly disable one lint rule
    - hooks_unuse_widget: false

Running flutter_hooks_lint in the terminal/CI πŸ€–

Custom lint rules created by flutter_hooks_lint may not show-up in dart analyze. To fix this, you can run a custom command line: custom_lint.

Since your project should already have custom_lint installed, then you should be able to run:

# Install custom_lint for project
dart pub get custom_lint
# run custom_lint's command line in a project
dart run custom_lint

Alternatively, you can globally install custom_lint:

# Install custom_lint for all projects
dart pub global activate custom_lint
# run custom_lint's command line in a project
custom_lint

All the lints

hooks_avoid_nesting

You should use Hooks only inside the build method of a Widget.

Bad:

@override
Widget build(BuildContext context) {
  if (isEnable) {
    final state = useState(0); // ❌
    return Text(state.value.toString());
  } else {
    return SizedBox.shrink();
  }
}

Good:

@override
Widget build(BuildContext context) {
    final state = useState(0); // β­•
    return isEnable ?
      Text(state.value.toString()) :
      SizedBox.shrink();
}

hooks_avoid_within_class

Defining Custom Hooks within a class mixes the characteristics of class and method, leading to potentially complex code.

Bad

class TestHelper {
  const TestHelper._();

  static void useEffectOnce(Dispose? Function() effect) { // ❌
    useEffect(effect, const []);
  }
}

Good:

void useEffectOnce(Dispose? Function() effect) { // β­•
  useEffect(effect, const []);
}

hooks_name_convention

DO always prefix your hooks with use.

Bad:

class WrongMethodWidget extends HookWidget {
  @override
  Widget build(BuildContext context) {
    effectOnce(() { // ❌
      return;
    });
    return Text('');
  }
}

Good:

class CorrectMethodWidget extends HookWidget {
  @override
  Widget build(BuildContext context) {
    useEffectOnce(() { // β­•
      return;
    });
    return Text('');
  }
}

hooks_extends

Using Hooks inside a Widget other than HookWidget or HookConsumerWidget will result in an error at runtime.

Bad:

class RequiresHookWidget extends StatelessWidget { // ❌
  @override
  Widget build(BuildContext context) {
    final state = useState(0);
    return Text(state.value.toString());
  }
}

class RequiresConsumerHookWidget extends ConsumerWidget { // ❌
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final state = useState(0);
    return Text(state.value.toString());
  }
}

Good:

class RequiresHookWidget extends HookWidget { // β­•
  @override
  Widget build(BuildContext context) {
    final state = useState(0);
    return Text(state.value.toString());
  }
}

class RequiresConsumerHookWidget extends HookConsumerWidget { // β­•
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final state = useState(0);
    return Text(state.value.toString());
  }
}

hooks_unuse_widget

If you are not using Hooks inside of a Widget, you do not need HookWidget or HookConsumerWidget.

Bad:

class UnuseHookWidget extends HookWidget { // ❌
  @override
  Widget build(BuildContext context) {
    return SizedBox.shrink();
  }
}

class UnuseHookConsumerWidget extends HookConsumerWidget { // ❌
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return SizedBox.shrink();
  }
}

Good:

class UnuseHookWidget extends StatelessWidget { // β­•
  @override
  Widget build(BuildContext context) {
    return SizedBox.shrink();
  }
}

class UnuseHookConsumerWidget extends ConsumerWidget { // β­•
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return SizedBox.shrink();
  }
}

hooks_memoized_consideration

Considering functionality, there may be places where it is worth considering the use of useMemoized.

Bad

class ConsiderationMemoizedWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final key = GlobalKey<TooltipState>(); // ❌
    final objectKey = GlobalObjectKey<TooltipState>("object"); // ❌
    return Column(
        children: [key, objectKey]
            .map((k) => Tooltip(
                  key: key,
                  message: 'Click me!',
                ))
            .toList());
  }
}

Good

class ConsiderationMemoizedWidget extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final key = useMemoized(() => GlobalKey<TooltipState>()); // β­•
    final objectKey = useMemoized(() => GlobalObjectKey<TooltipState>("object")); // β­•
    return Column(
        children: [key, objectKey]
            .map((k) => Tooltip(
                  key: key,
                  message: 'Click me!',
                ))
            .toList());
  }
}

hooks_callback_consideration

There are cases where you can use useCallback, which is the syntax sugar for useMemoized.

Bad

class ConsiderationMemoizedWidget extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final state = useMemoized(() => () => 0); // ❌
    return Text(state.call().toString());
  }
}

Good

class ConsiderationMemoizedWidget extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final state = useCallback(() => 0); // β­•
    return Text(state.call().toString());
  }
}

Contribution 🎁

Thanks for your interest! Issues and PR are welcomed! πŸ™Œ I would be delighted if you could translate the documentation into natural English or add new lint rules!

The project setup procedures for development are as follows:

  1. Fork it ( https://github.com/nikaera/flutter_hooks_lint/fork )
  2. Create your fix/feature branch (git checkout -b my-new-feature)
  3. Install packages ( flutter pub get )
  4. Run the test ( dart run grinder )
  5. Add a test each time you modify
  6. 4. it is possible to check the operation by executing the command ( dart run grinder )
  7. Commit your changes (git commit -am 'Add some feature')
  8. Push to the branch (git push origin my-new-feature)
  9. Create new Pull Request! πŸŽ‰