Skip to main content

useMemoizedIf

Computes and caches a value only while a condition holds. When the condition is false it returns null instead of running the block, and it recomputes whenever the condition or the keys change.

class OrderSummary extends HookWidget {
final Order order;
final bool isExpanded;

const OrderSummary({super.key, required this.order, required this.isExpanded});


Widget build(BuildContext context) {
// breakdown is computed only while expanded; it is null otherwise.
// It is recomputed when isExpanded or order changes.
final breakdown = useMemoizedIf(isExpanded, () => order.computeBreakdown(taxRate: 0.2), [order]);

return Column(
children: [
Text(order.title),
if (breakdown != null) Text('Total: ${breakdown.total}'), // <- null when collapsed
],
);
}
}

Signature

T? useMemoizedIf<T>(bool condition, T Function() block, [HookKeys keys = hookKeysEmpty]);

It is a thin wrapper over useMemoized: the result is useMemoized(() => condition ? block() : null, [condition, ...keys]). condition is prepended to the keys, so toggling it re-runs the memoization. The return type is always nullable, because null is the value when the condition is false.

Use cases

  • Deferring an expensive computation until it is actually needed, e.g. building a detail breakdown only while a section is expanded.
  • Producing a value to hand to another hook that treats null as a no-op, such as a stream built only once a prerequisite is ready:
    final stream = useMemoizedIf(isReady, () => buildStream(userId), [userId]);
    useStreamSubscription(stream, (event) async => handle(event)); // <- no subscription until isReady

Caveats

  • The keys parameter is positional, not named - unlike most hooks. Pass it as the third argument:

    // DON'T
    useMemoizedIf(isOpen, () => compute(id), keys: [id]); // <- no such named parameter

    // DO
    useMemoizedIf(isOpen, () => compute(id), [id]);
  • It is not a substitute for useIf. useMemoizedIf runs a plain computation, so the block must not call hooks. To run hooks conditionally, use useIf.

    // DON'T
    useMemoizedIf(isOpen, () => useAutoComputedState(() => load(id))); // <- hooks in the block

    // DO
    useIf(isOpen, () => useAutoComputedState(() => load(id)));
  • Like useMemoized, the result is cached - the block runs only when condition or a key changes, not on every build. If the block reads a value that is not in keys, the cached result goes stale.

See also

  • useMemoized - the unconditional version this hook is built on
  • useIf - when the block needs to call hooks, not just compute a value
  • usePreviousIfNull - another small hook for smoothing over transient nulls