React hooks in Flutter
If you write React, utopia_hooks is the Flutter state-management model that will feel most familiar. State is declared where logic runs, effects are keyed by dependencies, derived values are memoized, and custom hooks compose smaller hooks into a reusable unit.
The full migration guide is React to Flutter with hooks. This page is the short map.
React to Flutter mapping
| React | utopia_hooks | Notes |
|---|---|---|
useState(initial) | useState(initial) | Returns a mutable object; read and write through .value |
useEffect(fn, deps) | useEffect(fn, keys) | Return a cleanup function when needed |
useEffect(fn, []) | useEffect(fn) or useEffect(fn, []) | No keys means run once in utopia_hooks |
useMemo(fn, deps) | useMemoized(fn, keys) | Prefer this for derived state |
useCallback(fn, deps) | useMemoized(() => fn, keys) | A callback is a memoized function |
useContext(Context) | useProvided<T>() | Reads a provided value by type |
| Custom hook | Custom hook | Same idea: a function that calls hooks |
| Async data in an effect | useAutoComputedState | Avoid hand-rolled loading/error flags |
| List pagination | usePaginatedComputedState | Cursor/page/token pagination built in |
| Hook testing | SimpleHookContext | Unit-test hook state without pumping widgets |
The biggest differences
State is .value, not a tuple. React returns [value, setValue]; utopia_hooks returns one mutable state object.
final count = useState(0);
count.value++;
No keys means run once. In React, omitting the dependency array runs the effect after every render. In utopia_hooks, omitting keys is the empty-dependency case.
useEffect(() {
analytics.trackScreen('profile');
return null;
}); // runs once
Global state is part of the hook model. React's useContext is a primitive, but app state usually becomes Redux, Zustand, React Query, or a context stack. In utopia_hooks, shared state is a hook registered at the app root and read with useProvided<T>().
The View is separate. React components usually hold logic and JSX in the same function. In the recommended Flutter architecture, the state hook owns the logic and the View is a plain StatelessWidget.
What a React render function becomes
In React, a component often has state, effects, derived values, and returned UI in one function. In utopia_hooks, that logic becomes a state hook:
class ProfileScreenState {
final int count;
final String label;
final void Function() onIncrementPressed;
const ProfileScreenState({
required this.count,
required this.label,
required this.onIncrementPressed,
});
}
ProfileScreenState useProfileScreenState() {
final count = useState(0);
final label = useMemoized(() => 'count: ${count.value}', [count.value]);
useEffect(() {
analytics.trackScreen('profile');
return null;
});
return ProfileScreenState(
count: count.value,
label: label,
onIncrementPressed: () => count.value++,
);
}
The widget tree that React would return becomes a View:
class ProfileScreenView extends StatelessWidget {
final ProfileScreenState state;
const ProfileScreenView({required this.state});
Widget build(BuildContext context) {
return Column(
children: [
Text(state.label),
ElevatedButton(
onPressed: state.onIncrementPressed,
child: const Text('+'),
),
],
);
}
}
The coordinator Screen calls the hook once and returns the View. That split is the Screen / State / View pattern.
Where to go next
- Full guide: React to Flutter with hooks
- Hook basics: Basics
- Core hook catalog: Common hooks
- Global state: Global state
- Architecture: Flutter architecture with hooks
- Comparison: utopia_hooks vs flutter_hooks