avoid-conditional-hooks

Path
lint/correctness/avoid-conditional-hooks
Default
On (recommended)
Origin
Falcon
Rule
37 of 512
recommendedflutter_hooks

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

example.dartdart
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,
  };
}
Avoid calling hooks conditionally. Move this call outside the condition or loop.
6 if (enabled) {
7 useState(0);
Avoid calling hooks conditionally. Move this call outside the condition or loop.
8 } else {
9 useMemoized(() => 1);
Avoid calling hooks conditionally. Move this call outside the condition or loop.
11 final first = enabled
12 ? useState(1)
Avoid calling hooks conditionally. Move this call outside the condition or loop.
12 ? useState(1)
13 : useState(2);
Avoid calling hooks conditionally. Move this call outside the condition or loop.
13 : useState(2);
14 final and = enabled && useState(true);
Avoid calling hooks conditionally. Move this call outside the condition or loop.
14 final and = enabled && useState(true);
15 final or = enabled || useState(false);
Avoid calling hooks conditionally. Move this call outside the condition or loop.
16 int? optional;
17 final fallback = optional ?? useState(1);
Avoid calling hooks conditionally. Move this call outside the condition or loop.
17 final fallback = optional ?? useState(1);
18 optional ??= useState(2);
Avoid calling hooks conditionally. Move this call outside the condition or loop.
19 for (final value in values) {
20 useState(value);
Avoid calling hooks conditionally. Move this call outside the condition or loop.
22 while (enabled) {
23 useMemoized(() => 1);
Avoid calling hooks conditionally. Move this call outside the condition or loop.
26 do {
27 useState(0);
Avoid calling hooks conditionally. Move this call outside the condition or loop.
29 for (var index = 0; index < values.length; index++) {
30 useCounter();
Avoid calling hooks conditionally. Move this call outside the condition or loop.
31 }
32 final list = [if (enabled) useState(3)];
Avoid calling hooks conditionally. Move this call outside the condition or loop.
32 final list = [if (enabled) useState(3)];
33 final mapped = [for (final value in values) useState(value)];
Avoid calling hooks conditionally. Move this call outside the condition or loop.
33 final mapped = [for (final value in values) useState(value)];
34 final map = {if (enabled) 'value': useState(1)};
Avoid calling hooks conditionally. Move this call outside the condition or loop.
34 final map = {if (enabled) 'value': useState(1)};
35 final repeatedMap = {for (final value in values) value: useState(value)};
Avoid calling hooks conditionally. Move this call outside the condition or loop.
37 case 1:
38 useState(0);
Avoid calling hooks conditionally. Move this call outside the condition or loop.
41 final selected = switch (first) {
42 1 => useState(1),

Valid

example.dartdart
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.

falcon.jsonjson
{
  "linter": {
    "rules": {
      "correctness": {
        "avoid-conditional-hooks": "error"
      }
    }
  }
}

Or silence one occurrence with a suppression comment; the reason is required.

example.dartdart
// falcon-ignore lint/correctness/avoid-conditional-hooks: <reason>