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
Wrapping initial setup in setState. The framework builds this widget as soon
as initState returns, so the setState buys nothing and only adds a frame’s
worth of bookkeeping:
class _ProfilePageState extends State<ProfilePage> { String _title = '';
@override void initState() { super.initState(); setState(() { // LINT _title = widget.user.displayName; }); }
@override Widget build(BuildContext context) => Text(_title);}didUpdateWidget is the same — it is already part of a build:
class _ProfilePageState extends State<ProfilePage> { String _title = '';
@override void didUpdateWidget(ProfilePage oldWidget) { super.didUpdateWidget(oldWidget); setState(() { // LINT _title = widget.user.displayName; }); }
@override Widget build(BuildContext context) => Text(_title);}In build it is worse than redundant: it schedules another build from inside a
build, which at best wastes a frame and at worst never settles.
class _ProfilePageState extends State<ProfilePage> { int _renderCount = 0;
@override Widget build(BuildContext context) { setState(() { // LINT _renderCount++; }); return Text('$_renderCount'); }}Assign directly in the lifecycle methods:
class _ProfilePageState extends State<ProfilePage> { String _title = '';
@override void initState() { super.initState(); _title = widget.user.displayName; }
@override void didUpdateWidget(ProfilePage oldWidget) { super.didUpdateWidget(oldWidget); _title = widget.user.displayName; }
@override Widget build(BuildContext context) => Text(_title);}setState after an await is still required
Section titled “setState after an await is still required”An async method resumes long after its lifecycle method returned, so nothing has scheduled a build for it:
class _ProfilePageState extends State<ProfilePage> { String _title = '';
@override void initState() { super.initState(); _load(); }
Future<void> _load() async { final user = await fetchUser(); if (!mounted) return; setState(() { _title = user.displayName; }); }
@override Widget build(BuildContext context) => Text(_title);}setState in an event handler is fine
Section titled “setState in an event handler is fine”A tap handler written inside build runs after the build finished, so it does
need setState — and the rule leaves it alone:
class _CounterState extends State<Counter> { int _count = 0;
@override Widget build(BuildContext context) => ElevatedButton( onPressed: () => setState(() => _count++), child: Text('$_count'), );}Known limitations
Section titled “Known limitations”The event-handler exemption keys on the closure being a named argument —
onPressed:, onTap:, onChanged:. A callback passed positionally is
reported even though it behaves the same way.
Only initState, didUpdateWidget and build are checked. A setState in
didChangeDependencies is not reported, though the framework rebuilds after
that too.
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.