avoid_unnecessary_setstate
Warns when setState is called directly inside initState, didUpdateWidget, or build methods. In these lifecycle methods, mutating state directly is sufficient because the framework already schedules a build after they return. Calling setState in build triggers an unnecessary additional rebuild.
Why use this rule
Section titled “Why use this rule”In initState and didUpdateWidget, the framework will call build automatically after the method returns, so wrapping mutations in setState is redundant overhead. In build, calling setState triggers an infinite rebuild loop or at minimum a wasted frame. Event handler callbacks (like onTap) inside build are excluded from this rule since they run asynchronously and do need setState.
See also: setState
class _BadInitStateState extends State<BadInitState> { String _data = '';
@override void initState() { super.initState(); // Unnecessary — framework rebuilds after initState anyway setState(() { _data = 'Hello'; }); }
@override Widget build(BuildContext context) => const SizedBox();}
class _BadBuildState extends State<BadBuild> { String _data = '';
@override Widget build(BuildContext context) { // Triggers an unnecessary rebuild during build setState(() { _data = 'Hello'; }); return const SizedBox(); }}class _GoodInitStateState extends State<GoodInitState> { String _data = '';
@override void initState() { super.initState(); _data = 'Hello'; // Assign directly — no setState needed }
@override Widget build(BuildContext context) => const SizedBox();}
// setState in an async method is fineclass _GoodAsyncState extends State<GoodAsync> { String _data = '';
Future<void> _loadData() async { final data = await Future.value('Hello'); setState(() { _data = data; // Async method needs setState to trigger rebuild }); }
@override Widget build(BuildContext context) => const SizedBox();}
// setState in event handler callbacks inside build is fineclass _GoodCallbackState extends State<GoodCallback> { String _data = '';
@override Widget build(BuildContext context) { return GestureDetector( onTap: () { setState(() { _data = 'tapped'; // Event handler runs asynchronously }); }, child: const SizedBox(), ); }}Turning this rule off
Section titled “Turning this rule off”This rule is in the recommended preset, so it is on with
preset: recommended or preset: opinionated. Add it to preset: core with
avoid_unnecessary_setstate: true.
To turn it off:
rules: avoid_unnecessary_setstate: falseTo keep the rule on but skip certain paths, use per-rule exclude.
Options
Section titled “Options”Projects with a state abstraction that does not extend Flutter’s State can
opt that base class into this rule:
many_lints: rules: avoid_unnecessary_setstate: state_base_classes: [AppState]rules: avoid_unnecessary_setstate: state_base_classes: [AppState]| Option | Type | Default | Description |
|---|---|---|---|
state_base_classes |
list of strings | [] |
Additional non-State base classes whose subclasses should be treated as state classes |
Related rules
Section titled “Related rules”avoid_empty_setstate— Don’t call setState with an empty callback.avoid_mounted_in_setstate— Detect mounted checks inside setState callbacks.avoid_inherited_widget_in_initstate— Don’t look up inherited widgets inside initState.avoid_late_context— Don’t read BuildContext in a late field initializer.