Skip to content

prefer_use_callback

v0.4.0 Warning Fix Hook Rules

This rule flags useMemoized(() => someFunction) — a useMemoized whose factory returns a function. useCallback exists for exactly that, and the quick fix swaps it in.

useMemoized(() => () { ... }) has two layers of closure and only one of them means anything: the outer one is ceremony the hook demands, the inner one is the callback you wanted. useCallback takes the callback directly, so what you read is what gets memoised.

See also: flutter_hooks — useCallback

import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
class SubmitButton extends HookWidget {
const SubmitButton({super.key});
@override
Widget build(BuildContext context) {
// A closure wrapped in a closure
final onPressed = useMemoized( // LINT
() => () {
debugPrint('submitted');
},
);
return ElevatedButton(onPressed: onPressed, child: const Text('Submit'));
}
}
class SubmitButton extends HookWidget {
const SubmitButton({super.key});
@override
Widget build(BuildContext context) {
final onPressed = useCallback(() {
debugPrint('submitted');
}, const []);
return ElevatedButton(onPressed: onPressed, child: const Text('Submit'));
}
}
// Don't
final onTap = useMemoized(() => _handleTap); // LINT
// Do
final onTap = useCallback(_handleTap, const []);

useMemoized is right for non-function values

Section titled “useMemoized is right for non-function values”

Nothing is reported when the factory returns data — that is what the hook is for:

// Not reported
final rows = useMemoized(
() => items.map((i) => i.toUpperCase()).toList(),
[items],
);
final total = useMemoized(() => items.length * 2, [items]);

Only a single-expression or single-return factory is checked. A factory that does other work before returning the closure is left alone, even though useCallback may still fit:

// Not reported — the block has more than one statement
final onTap = useMemoized(() {
final label = title.toUpperCase();
return () => debugPrint(label);
});

The hook is matched by name. useMemoized and _useMemoized are recognised wherever they are declared; a memoisation helper under another name is not.

The factory must be written inline. useMemoized(buildCallback), passing an existing function rather than a literal closure, is not inspected.

This rule is in the opinionated preset, so it is on with preset: opinionated, or by name:

many_lints.yaml
rules:
prefer_use_callback: true

To turn it off again:

many_lints.yaml
rules:
prefer_use_callback: false

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