Hooks, meet Flutter. Inspired by React Hooks and Dan Abramov’s piece, Making sense of React Hooks, the developers at Dash Overflow decided to bring Hooks into Flutter.
Flutter widgets behave similarly to React components, as many of the lifecycles in a React component are present in a Flutter widget. According to the creators on their GitHub page:
Hooks are a new kind of object that manages Widget life-cycles. They exist for one reason: increase the code-sharing between widgets by removing duplicates.
The flutter_hooks library provides a robust and clean way to manage a widget’s lifecycle by increasing code-sharing between widgets and reducing duplicates in code.
The built-in Flutter Hooks include:
useEffectuseStateuseMemoizeduseRefuseCallbackuseContextuseValueChangedIn this post, we’ll focus on three of these Hooks:
useState Hook manages local states in appsuseEffect Hook fetches data from a server and sets the fetch to the local stateuseMemoized Hook memoizes heavy functions to achieve optimal performance in an appWe’ll also learn how to create and use custom Hooks from flutter_hooks as well.
Now, let’s see how we can install the flutter_hooks library below.
The Replay is a weekly newsletter for dev and engineering leaders.
Delivered once a week, it's your curated guide to the most important conversations around frontend dev, emerging AI tools, and the state of modern software.
flutter_hooks libraryTo use Flutter Hooks from the flutter_hooks library, we must install it by running the following command in a terminal inside a Flutter project:
flutter pub add flutter_hooks
This adds flutter_hooks: VERSION_NUMER_HERE in the pubspec.yaml file in the dependencies section.
Also, we can add flutter_hooks into the dependencies section in the pubspec.yaml file:
dependencies:
flutter:
sdk: flutter
flutter_hooks:
After saving the file, Flutter installs the dependency. Next, import the flutter_hooks library:
import 'package:flutter_hooks/flutter_hooks.dart';
Now we are good to go!
useState HookJust like useState in React, useState in Flutter helps us create and manage state in a widget.
The useState Hook is called with the state we want to manage locally in a widget. This state passes to the useState Hook as a parameter. This state is the initial state because it can change during the lifetime of the widget:
final state = useState(0);
Here, 0 passes to useState and becomes the initial state.
Now, let’s see how we can use it in a widget. We must first convert Flutter’s counter example to use useState.
Here is Flutter’s original counter example:
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
Note that using the StatefulWidget makes maintaining state locally in a widget complex at times. We must also introduce another class that extends a State class, creating two classes for a StatefulWidget.
However, with Hooks, we only use one class to maintain our code, making it easier to maintain than StatefulWidget.
Below is the Hook equivalent:
class MyHomePage extends HookWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
Widget build(BuildContext context) {
final _counter = useState(0);
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter.value',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => _counter.value++,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
The Hook example is shorter than its contemporary. Before using Flutter Hooks in a widget, however, the widget must extend HookWidget, which is provided by the flutter_hooks library.
By calling useState in the build method with 0, we store the returned value in _counter. This _counter is an instance of ValueNotifier.
The state is now stored at the .value property of the ValueNotifier. So, the value of the _counter state is stored at _counter.value.
useState subscribes to the state in the .value property and when the value at .value is modified, the useState Hook rebuilds the widget to display the new value.
In the FloatingActionButton, the _counter.value increments if the button is pressed. This makes the state increase by 1, and useState rebuilds the MyHomePage widget to display the new value.
useEffect HookThe useEffect Hook in Flutter is the same as React’s useEffect Hook. The Hook takes a function callback as a parameter and runs side effects in a widget:
useEffect( () {
// side effects code here.
//subscription to a stream, opening a WebSocket connection, or performing HTTP requests
});
Side effects can include a stream subscription, opening a WebSocket connection, or performing HTTP requests. They’re also done inside the Hook, so we can cancel them when a widget is disposed of.
The function callback must return a function and is called when the widget is disposed of. We can then cancel subscriptions or other cleanups in that function before the widget is removed from the UI and widget tree. Other cleanups include:
This prevents open connections — such as HTTP, WebSocket connections, open streams, and open subscriptions — in the widget from sticking around after the widget that opened them is destroyed and no longer in the widget tree:
useEffect( () {
// side effects code here.
// - Unsubscribing from a stream.
// - Cancelling polling
// - Clearing timeouts
// - Cancelling active HTTP connections.
// - Cancelling WebSockets conncetions.
return () {
// clean up code
}
});
The function callback in useEffect is called synchronously, meaning it’s called every time the widget renders or rerenders.
keys argument for useEffectThis Hook also has an optional second argument named keys. The keys argument is a list of values that determine whether the function callback in the useEffect Hook will be called or not.
useEffect compares the current values of keys against its previous values. If the values are different, useEffect runs the function callback. If only one value in keys remains the same, the function callback is not called:
useEffect( () {
// side effects code here.
return () {
// clean up code
}
}, [keys]);
useMemoized HookThe useMemoized Hook is like useMemo in React: it memoizes/caches the instance of complex objects created from a builder function.
This function passes to the useMemoized Hook, then useMemoized calls and stores the result of the function. If a widget rerendering the function is not called, useMemoized is called and its previous result returns.
keys argument for useMemoizedSimilar to useEffect, the useMemoized Hook has a second optional argument called the keys:
const result = useMemoized(() {}, [keys]);
This keys argument is a list of dependencies, which determine whether the function passed to useMemoized executes when the widget rerenders.
When a widget rebuilds, useMemoized checks its keys to see whether the previous values changed. If at least one value changed, the function callback in the useMemoized Hook will be called, and the result renders the function call result.
If none of the values changed since they were last checked, useMemoized skips calling the function and uses its last value.
flutter_hooks enables us to create our own custom Hooks through two methods: a function or class.
When creating custom Hooks, there are two rules to follow:
use as a prefix tells developers that the function is a Hook, not a normal functionUsing the function and class methods, we will create a custom Hook that prints a value with its debug value, just like React’s useDebugValue Hook.
Let’s begin with the function method.
To begin with the function method, we must create a method using any of the built-in Hooks inside it:
ValueNotifier<T> useDebugValue([T initialState],debugLabel) {
final state = useState(initialState);
print(debugLabel + ": " + initialState);
return state;
}
In the above code, using the built-in useState Hook holds the state in the function and prints the state’s debugLabel and value.
We can then return the state. So, using debugLabel, the state’s label prints in the console when the widget is mounted to the widget tree for the first time and when modifying the state value.
Next, let’s see how to use the useDebugValue Hook we created to print the debutLabel string and corresponding state when mounting and rebuilding the widget:
final counter = useDebugValue(0, "Counter"); final score = useDebugValue(10, "Score"); // Counter: 0 // Score: 10
Now, let’s use a class to recreate the useDebugValue custom Hook. This is done by creating a class that extends a Hook class:
ValueNotifier<T> useDebugValue<T>(T initialData, debugLabel) {
return use(_StateHook(initialData: initialData, debugLabel));
}
class _StateHook<T> extends Hook<ValueNotifier<T>> {
const _StateHook({required this.initialData, this.debugLabel});
final T debugLabel;
final T initialData;
@override
_StateHookState<T> createState() => _StateHookState();
}
class _StateHookState<T> extends HookState<ValueNotifier<T>, _StateHook<T>> {
late final _state = ValueNotifier<T>(hook.initialData)
..addListener(_listener);
@override
void dispose() {
_state.dispose();
}
@override
ValueNotifier<T> build(BuildContext context) {
print(this.debugLabel + ": " + _state.value);
return _state;
}
void _listener() {
setState(() {});
}
}
In the above code, we have the useDebugValue function, which is our custom Hook. It accepts arguments, such as the initialData initial state value the Hook manages, and the state’s label, debugLabel.
The _StateHook class is where our Hook logic is written. When the use function is called and passed in the _StateHook class instance, it registers the _StateHook class to the Flutter runtime. We can then call useDebugLabel as a Hook.
So, whenever creating a Hook using the class method, the class must extend a Hook class. You can also use Hook.use() in place of use().
flutter_hooks brought a major change in how we build Flutter widgets by helping reduce the size of a codebase to a considerably smaller size.
As we have seen, flutter_hooks enables developers to do away with widgets like StatefulWidget, allowing them to write clean and maintainable code that’s easy to share and test.
Install LogRocket via npm or script tag. LogRocket.init() must be called client-side, not
server-side
$ npm i --save logrocket
// Code:
import LogRocket from 'logrocket';
LogRocket.init('app/id');
// Add to your HTML:
<script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
<script>window.LogRocket && window.LogRocket.init('app/id');</script>

:has(), with examplesThe CSS :has() pseudo-class is a powerful new feature that lets you style parents, siblings, and more – writing cleaner, more dynamic CSS with less JavaScript.

Kombai AI converts Figma designs into clean, responsive frontend code. It helps developers build production-ready UIs faster while keeping design accuracy and code quality intact.

Discover what’s new in The Replay, LogRocket’s newsletter for dev and engineering leaders, in the October 22nd issue.

John Reilly discusses how software development has been changed by the innovations of AI: both the positives and the negatives.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up now
2 Replies to "How to use Flutter Hooks"
Custom hooks are used to combine many primary hooks. There is no point in creating custom hook with a single primary hook. Can you add one more example for a custom hook with two primary hooks?
‘$_counter.value’ doesn’t work! it should be ‘${_counter.value}