Skip to main content

useIf

Runs a block of hooks only while a condition is true. It returns their result as T? - the value while the condition holds, null once it flips to false, at which point the inner hooks are disposed.

class SearchPanel extends HookWidget {
const SearchPanel({super.key});


Widget build(BuildContext context) {
final queryState = useState('');
final hasQuery = queryState.value.isNotEmpty;

// The search hook only exists while there is a query. Clearing the field
// disposes it; the result type is List<String>? - null when collapsed.
final resultsState = useIf(
hasQuery,
() => useAutoComputedState(
() => searchService.search(queryState.value),
keys: [queryState.value],
),
);

return Column(
children: [
TextField(onChanged: (value) => queryState.value = value),
Text(resultsState?.valueOrNull?.join(', ') ?? 'Type to search'),
],
);
}
}

This is the everyday way to run hooks conditionally, which the rules of hooks otherwise forbid. A common shape is a hook that should only do work once something is opened, selected, or enabled: gate it behind useIf and the work starts when the flag turns true and is torn down when it turns false.

Signature

T? useIf<T>(bool condition, T Function() block);
  • condition - while true, block runs and its result is returned; while false, the inner hooks are disposed and the result is null.
  • block - runs during build and may call hooks.

Under the hood it is useKeyed([condition], () => condition ? block() : null) - the nested hook context is keyed on the boolean, so each flip rebuilds or disposes the inner hooks.

Use cases

  • Lazily starting an async load when a tile is expanded or a section becomes visible, and dropping it when collapsed.
  • Running a subscription or controller only while a feature is enabled.

Caveats

  • The result is nullable. The inner hooks do not exist while condition is false, so guard the return value (result?.value) rather than assuming it is present.
  • Flipping the condition disposes everything the block created - any useState resets, any in-flight work is dropped. If you need to keep state while the value merely changes (rather than crosses a boolean edge), reach for useIfNotNull or useKeyed with finer keys.
  • For a conditional value rather than conditional hooks, use useMemoizedIf - it avoids a nested context when no hooks need to run inside.

See also

  • useKeyed - the primitive behind useIf, plus the useIfNotNull / useLet family
  • useMemoizedIf - a conditional value when no inner hooks are needed
  • useMap - conditional hooks keyed by a whole set, one context per key