useMemoized
Caches the result of a synchronous function and returns it on subsequent builds, recomputing only when any of the keys change. An optional dispose callback can clean up the previous result.
class SortedNames extends HookWidget {
final List<String> names;
const SortedNames({super.key, required this.names});
Widget build(BuildContext context) {
// Re-sorts only when the names list changes, not on every build.
final sorted = useMemoized(
() => [...names]..sort(),
[names],
);
return Text(sorted.join(', '));
}
}
Signature
T useMemoized<T>(T Function() block, [HookKeys keys = hookKeysEmpty, void Function(T)? dispose]);
block- the synchronous computation. Its result is cached and returned until the keys change.keys- default is an empty list, which never changes, so by defaultblockruns once and the result is reused for the lifetime of the hook. List values here to recompute when they change.dispose- optional callback invoked with the old value before recomputing, and once more when the hook's context is destroyed.
Use cases
- Caching the result of a synchronous computation that is expensive to compute
- Creating long-lived objects (like Flutter controllers) that should be built once and disposed automatically
class SearchField extends HookWidget {
const SearchField({super.key});
Widget build(BuildContext context) {
// Created once and disposed when the hook context is destroyed.
final controller = useMemoized(
TextEditingController.new,
const [],
(it) => it.dispose(), // <- dispose callback, called on teardown / key change
);
return TextField(controller: controller);
}
}
For deriving state from other hooks, useMemoized is the right tool - it avoids the common anti-pattern of a useState written from a useEffect:
// sortedBy comes from package:collection (an IterableExtension)
// DON'T - useEffect to derive state: extra variable, extra rebuild
final sortedState = useState<IList<Task>?>(null);
useEffect(() {
sortedState.value = tasks.sortedBy((it) => it.dueDate).toIList();
return null;
}, [tasks]);
// DO - useMemoized: direct, no extra state
final sorted = useMemoized(() => tasks.sortedBy((it) => it.dueDate).toIList(), [tasks]);
Caveats
-
The computation runs during the build, so if it is really expensive it can still cause jank. Consider moving it to an effect to run it after the build, or to a separate isolate altogether.
-
blockmust be a synchronous, pure computation - do not call other hooks inside it. To memoize an async result, pairuseMemoizedwithuseFuture, or use the dedicateduseMemoizedFuture/useMemoizedStreamhooks (see the Async category). -
The dispose callback fires every time the keys change (on the previous value), not only at teardown. When you pass a
dispose, make sure recreating the value on a key change is what you want.
See also
- useState - own a value instead of deriving it
- useEffect - run side effects rather than compute a value
- useValueChanged - compute from the previous value when a value changes
- Common hooks -
useMemoizedtogether withuseFuture/useStream