avoid-conditional-hooks
- Group
- Correctness
- Path
lint/correctness/avoid-conditional-hooks- Default
- On (recommended)
- Origin
- Falcon
- Domain
- flutter_hooks
- Rule
- 37 of 512
Flags hook calls in conditional branches, short-circuit operands, and loops.
Hooks depend on a stable invocation order across builds. Move the hook call
outside the condition and put conditional work inside its callback instead.
Only hook-widget builds, custom useX functions, and HookBuilder builders
are checked; invalid hook contexts belong to the other hook rules.
Package hooks and custom hooks proven to compose them are recognized by
semantic identity. If/ternary conditions, switch subjects, left operands,
and for-loop initializers/iterables are unconditional. Nested functions
start separate execution contexts. This rule checks enclosing syntax, not
conditional early exits, exception paths, lazy variables, or null-aware calls.
Invalid
import 'package:flutter_hooks/flutter_hooks.dart';
int useCounter() => useState(0);
void useConditional(bool enabled, List<int> values) {
if (enabled) {
useState(0);
} else {
useMemoized(() => 1);
}
final first = enabled
? useState(1)
: useState(2);
final and = enabled && useState(true);
final or = enabled || useState(false);
int? optional;
final fallback = optional ?? useState(1);
optional ??= useState(2);
for (final value in values) {
useState(value);
}
while (enabled) {
useMemoized(() => 1);
break;
}
do {
useState(0);
} while (enabled);
for (var index = 0; index < values.length; index++) {
useCounter();
}
final list = [if (enabled) useState(3)];
final mapped = [for (final value in values) useState(value)];
final map = {if (enabled) 'value': useState(1)};
final repeatedMap = {for (final value in values) value: useState(value)};
switch (first) {
case 1:
useState(0);
break;
}
final selected = switch (first) {
1 => useState(1),
_ => 0,
};
} if (enabled) { useState(0); } else { useMemoized(() => 1); final first = enabled ? useState(1) ? useState(1) : useState(2); : useState(2); final and = enabled && useState(true); final and = enabled && useState(true); final or = enabled || useState(false); int? optional; final fallback = optional ?? useState(1); final fallback = optional ?? useState(1); optional ??= useState(2); for (final value in values) { useState(value); while (enabled) { useMemoized(() => 1); do { useState(0); for (var index = 0; index < values.length; index++) { useCounter(); } final list = [if (enabled) useState(3)]; final list = [if (enabled) useState(3)]; final mapped = [for (final value in values) useState(value)]; final mapped = [for (final value in values) useState(value)]; final map = {if (enabled) 'value': useState(1)}; final map = {if (enabled) 'value': useState(1)}; final repeatedMap = {for (final value in values) value: useState(value)}; case 1: useState(0); final selected = switch (first) { 1 => useState(1),Valid
import 'package:flutter_hooks/flutter_hooks.dart' as hooks;
int useCounter() => hooks.useState(0);
void useStable(bool enabled, List<int> values) {
final state = hooks.useState(0);
hooks.useMemoized(() {
if (enabled) return 1;
return 0;
});
hooks.useEffect(() {
for (final value in values) {
if (value > 0) print(value);
}
});
final condition = hooks.useState(true) && enabled;
final otherCondition = hooks.useState(false) || enabled;
final int? optional = hooks.useState<int?>(null);
final fallback = optional ?? 1;
final ternary = hooks.useState(true) ? 1 : 0;
if (hooks.useState(true)) print(state);
for (var index = hooks.useState(0); index < 2; index++) {
print(index);
}
final custom = useCounter();
final collection = [if (hooks.useState(true)) custom];
if (enabled) {
void callback() {
hooks.useState(0);
}
final callbackExpression = () => hooks.useState(0);
}
}
class Unrelated {
int useState(int initial) => initial;
void useUnrelated(bool enabled) {
if (enabled) useState(0);
}
}
void unrelated(bool enabled, Unrelated service) {
if (enabled) service.useState(0);
}
void useShadowed(bool enabled, int Function(int) useState) {
if (enabled) useState(0);
}Configure avoid-conditional-hooks
Set its severity in falcon.json. See the configuration guide.
{
"linter": {
"rules": {
"correctness": {
"avoid-conditional-hooks": "error"
}
}
}
}Or silence one occurrence with a suppression comment; the reason is required.
// falcon-ignore lint/correctness/avoid-conditional-hooks: <reason>More correctness rules
- avoid-constant-assert-conditions
Flags assertions whose Boolean condition is statically constant.
- avoid-constant-conditions
Flags statically constant binary branch, loop, collection, and expression conditions.
- avoid-constant-switches
Flags switch statements and expressions with a statically constant subject.
- avoid-contradictory-expressionsrecommended
Flags pairs of conditions that are always false with
&&or true with||. - avoid-disposing-late-fieldsflutter
Avoid releasing late State fields that may be unset or initialized only by cleanup.
- avoid-duplicate-bloc-event-handlersrecommendedbloc
Flags later registrations of the same event type in a Bloc constructor.
- avoid-duplicate-cascades
Flags repeated field or index assignment targets within one cascade.
- avoid-duplicate-collection-elements
Flags repeated scalar literals and stable local reads in list and set literals.