Skip to content

setState vs useState

In React, useState is a hook that stores a value and returns a setter. Call the setter, React schedules a re-render, the component function runs again with the new value. The state lives in a hidden slot attached to the component instance.

Flutter’s equivalent is setState(() { ... }). But before you can call it, you need two classes instead of one — a StatefulWidget and a State<T>. That split looks verbose at first, but it solves a specific problem: widget objects in Flutter are immutable and may be recreated cheaply by the framework. The State object is longer-lived and holds the actual mutable data.

React
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}
Flutter
class Counter extends StatefulWidget {
const Counter({super.key});
@override
State<Counter> createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int _count = 0; // state lives here
void _increment() {
setState(() { // wraps the mutation
_count++;
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Count: $_count'),
ElevatedButton(
onPressed: _increment,
child: const Text('Increment'),
),
],
);
}
}
import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Counter',
      theme: ThemeData(colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo)),
      home: const Scaffold(
        body: Center(child: Counter()),
      ),
    );
  }
}

class Counter extends StatefulWidget {
  const Counter({super.key});

  @override
  State<Counter> createState() => _CounterState();
}

class _CounterState extends State<Counter> {
  int _count = 0;

  void _increment() => setState(() => _count++);
  void _decrement() => setState(() => _count--);
  void _reset()     => setState(() => _count = 0);

  @override
  Widget build(BuildContext context) {
    final scheme = Theme.of(context).colorScheme;
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Text(
          '$_count',
          style: TextStyle(
            fontSize: 72,
            fontWeight: FontWeight.bold,
            color: scheme.primary,
          ),
        ),
        const SizedBox(height: 24),
        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            FilledButton.tonal(onPressed: _decrement, child: const Text('−')),
            const SizedBox(width: 12),
            FilledButton(onPressed: _increment, child: const Text('+')),
            const SizedBox(width: 12),
            OutlinedButton(onPressed: _reset, child: const Text('Reset')),
          ],
        ),
      ],
    );
  }
}
What does Flutter's setState() callback do that React's useState setter does not?
How many times is createState() called for a single Counter widget instance?
Where do mutable state fields live in a Flutter StatefulWidget?
What is the React equivalent of Flutter's State<T>.build()?