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. | β |
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
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
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
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();
}
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 []);
}
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('');
}
}
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());
}
}
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();
}
}
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 HooksWidget {
@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());
}
}
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());
}
}
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:
- Fork it ( https://github.com/nikaera/flutter_hooks_lint/fork )
- Create your fix/feature branch (git checkout -b my-new-feature)
- Install Melos (
dart pub global activate melos
) - Set up the project and run the test (
melos bs
) - Add a test each time you modify
4.
it is possible to check the operation by executing the command (melos bs
)- Commit your changes (git commit -am 'Add some feature')
- Push to the branch (git push origin my-new-feature)
- Create new Pull Request! π