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- whiletrue,blockruns and its result is returned; whilefalse, the inner hooks are disposed and the result isnull.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
conditionisfalse, so guard the return value (result?.value) rather than assuming it is present. - Flipping the condition disposes everything the block created - any
useStateresets, any in-flight work is dropped. If you need to keep state while the value merely changes (rather than crosses a boolean edge), reach foruseIfNotNulloruseKeyedwith 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 theuseIfNotNull/useLetfamily - useMemoizedIf - a conditional value when no inner hooks are needed
- useMap - conditional hooks keyed by a whole set, one context per key