---
title: About hooks
version: 1
---

import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
import hookAndConsumer from "!!raw-loader!./about_hooks/hook_and_consumer.dart";
import hookConsumer from "!!raw-loader!./about_hooks/hook_consumer.dart";
import hookConsumerWidget from "!!raw-loader!./about_hooks/hook_consumer_widget.dart";
import { CodeSnippet } from "/src/components/CodeSnippet";
import { Link } from "/src/components/Link";

This page explains what are hooks and how they are related to Riverpod.

"Hooks" are utilities common from a separate package, independent from Riverpod:
[flutter_hooks].  
Although [flutter_hooks] is a completely separate package and does not have anything
to do with Riverpod (at least directly), it is common to pair Riverpod
and [flutter_hooks] together.

## Should you use hooks?

Hooks are a powerful tool, but they are not for everyone.  
If you are a newcomer to Riverpod, you should probably avoid using hooks.

Although useful, hooks are not necessary for Riverpod.  
You shouldn't start using hooks because of Riverpod. Rather, you should start
using hooks because you want to use hooks.

Using hooks is a tradeoff. They can be great for producing robust and reusable
code, but they are also a new concept to learn, and they can be confusing at first.
Hooks aren't a core Flutter concept. As such, they will feel out of place in Flutter/Dart.

## What are hooks?

Hooks are functions used inside widgets. They are designed as an alternative
to [StatefulWidget]s, to make logic more reusable and composable.

Hooks are a concept coming from [React](https://reactjs.org/), and [flutter_hooks]
is merely a port of the React implementation to Flutter.  
As such, yes, hooks may feel a bit out of place in Flutter. Ideally,
in the future we would have a solution to the problem that hooks solves,
designed specifically for Flutter.

If Riverpod's providers are for "global" application state, hooks are for
local widget state. Hooks are typically used for dealing with stateful UI objects,
such as [TextEditingController](https://api.flutter.dev/flutter/widgets/TextEditingController-class.html),
[AnimationController](https://api.flutter.dev/flutter/animation/AnimationController-class.html).  
They can also serve as a replacement to the "builder" pattern, replacing widgets
such as [FutureBuilder](https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html)/[TweenAnimatedBuilder](https://api.flutter.dev/flutter/widgets/TweenAnimationBuilder-class.html)
by an alternative that does not involve "nesting" – drastically improving readability.

In general, hooks are helpful for:

- Forms
- Animations
- Reacting to user events
- etc.

As an example, we could use hooks to manually implement a fade-in animation,
where a widget starts invisible and slowly appears.

If we were to use [StatefulWidget], the code would look like this:

```dart
class FadeIn extends StatefulWidget {
  const FadeIn({Key? key, required this.child}) : super(key: key);

  final Widget child;

  @override
  State<FadeIn> createState() => _FadeInState();
}

class _FadeInState extends State<FadeIn> with SingleTickerProviderStateMixin {
  late final AnimationController animationController = AnimationController(
    vsync: this,
    duration: const Duration(seconds: 2),
  );

  @override
  void initState() {
    super.initState();
    animationController.forward();
  }

  @override
  void dispose() {
    animationController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: animationController,
      builder: (context, child) {
        return Opacity(
          opacity: animationController.value,
          child: widget.child,
        );
      },
    );
  }
}
```

Using hooks, the equivalent would be:

```dart
class FadeIn extends HookWidget {
  const FadeIn({Key? key, required this.child}) : super(key: key);

  final Widget child;

  @override
  Widget build(BuildContext context) {
    // Create an AnimationController. The controller will automatically be
    // disposed when the widget is unmounted.
    final animationController = useAnimationController(
      duration: const Duration(seconds: 2),
    );

    // useEffect is the equivalent of initState + didUpdateWidget + dispose.
    // The callback passed to useEffect is executed the first time the hook is
    // invoked, and then whenever the list passed as second parameter changes.
    // Since we pass an empty const list here, that's strictly equivalent to `initState`.
    useEffect(() {
      // start the animation when the widget is first rendered.
      animationController.forward();
      // We could optionally return some "dispose" logic here
      return null;
    }, const []);

    // Tell Flutter to rebuild this widget when the animation updates.
    // This is equivalent to AnimatedBuilder
    useAnimation(animationController);

    return Opacity(
      opacity: animationController.value,
      child: child,
    );
  }
}
```

There are a few interesting things to note in this code:

- There is no memory leak. This code does not recreate a new `AnimationController` whenever the
  widget rebuilds, and the controller is correctly released when the widget is unmounted.

- It is possible to use hooks as many time as we want within the same widget.
  As such, we can create multiple `AnimationController` if we want:

  ```dart
  @override
  Widget build(BuildContext context) {
    final animationController = useAnimationController(
      duration: const Duration(seconds: 2),
    );
    final anotherController = useAnimationController(
      duration: const Duration(seconds: 2),
    );

    ...
  }
  ```

  This creates two controllers, without any sort of negative consequence.

- If we wanted, we could refactor this logic into a separate reusable function:

  ```dart
  double useFadeIn() {
    final animationController = useAnimationController(
      duration: const Duration(seconds: 2),
    );
    useEffect(() {
      animationController.forward();
      return null;
    }, const []);
    useAnimation(animationController);
    return animationController.value;
  }
  ```

  We could then use this function inside our widgets, as long as that widget is a [HookWidget]:

  ```dart
  class FadeIn extends HookWidget {
    const FadeIn({Key? key, required this.child}) : super(key: key);

    final Widget child;

    @override
    Widget build(BuildContext context) {
      final fade = useFadeIn();

      return Opacity(opacity: fade, child: child);
    }
  }
  ```

  Note how our `useFadeIn` function is completely independent from our
  `FadeIn` widget.  
  If we wanted, we could use that `useFadeIn` function in a completely different
  widget, and it would still work!

## The rules of hooks

Hooks comes with unique constraints:

- They can only be used within the `build` method of a widget that extends [HookWidget]:

  **Good**:

  ```dart
  class Example extends HookWidget {
    @override
    Widget build(BuildContext context) {
      final controller = useAnimationController();
      ...
    }
  }
  ```

  **Bad**:

  ```dart
  // Not a HookWidget
  class Example extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
      final controller = useAnimationController();
      ...
    }
  }
  ```

  **Bad**:

  ```dart
  class Example extends HookWidget {
    @override
    Widget build(BuildContext context) {
      return ElevatedButton(
        onPressed: () {
          // Not _actually_ inside the "build" method, but instead inside
          // a user interaction lifecycle (here "on pressed").
          final controller = useAnimationController();
        },
        child: Text('click me'),
      );
    }
  }
  ```

- They cannot be used conditionally or in a loop.

  **Bad**:

  ```dart
  class Example extends HookWidget {
    const Example({required this.condition, super.key});
    final bool condition;
    @override
    Widget build(BuildContext context) {
      if (condition) {
        // Hooks should not be used inside "if"s/"for"s, ...
        final controller = useAnimationController();
      }
      ...
    }
  }
  ```

For more information about hooks, see [flutter_hooks].

## Hooks and Riverpod

### Installation

Since hooks are independent from Riverpod, it is necessary to install hooks
separately. If you want to use them, installing [hooks_riverpod] is not
enough. You will still need to add [flutter_hooks] to your dependencies.
See <Link documentID="introduction/getting_started" hash="installing-the-package" />) for more information.

### Usage

In some cases, you may want to write a Widget that uses both hooks and Riverpod.
But as you may have already noticed, both hooks and Riverpod provide their
own custom widget base type: [HookWidget] and [ConsumerWidget].  
But classes can only extend one superclass at a time.

To solve this problem, you can use the [hooks_riverpod] package.
This package provides a [HookConsumerWidget] class that combines both
[HookWidget] and [ConsumerWidget] into a single type.  
You can therefore subclass [HookConsumerWidget] instead of [HookWidget]:

<CodeSnippet snippet={hookConsumerWidget}></CodeSnippet>

Alternatively, you can use the "builders" provided by both packages.  
For example, we could stick to using `StatelessWidget`, and use both
`HookBuilder` and `Consumer`.

<CodeSnippet snippet={hookAndConsumer}></CodeSnippet>

:::note
This approach would work without using [hooks_riverpod]. Only [flutter_riverpod]
is needed.
:::

If you like this approach, [hooks_riverpod] streamlines it by providing [HookConsumer],
which is the combination of both builders in one:

<CodeSnippet snippet={hookConsumer}></CodeSnippet>

[hookwidget]: https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/HookWidget-class.html
[hookconsumer]: https://pub.dev/documentation/hooks_riverpod/latest/hooks_riverpod/HookConsumer-class.html
[hookconsumerwidget]: https://pub.dev/documentation/hooks_riverpod/latest/hooks_riverpod/HookConsumerWidget-class.html
[consumerwidget]: https://pub.dev/documentation/flutter_riverpod/latest/flutter_riverpod/ConsumerWidget-class.html
[statefulwidget]: https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html
[riverpod]: https://github.com/rrousselgit/riverpod
[hooks_riverpod]: https://pub.dev/packages/hooks_riverpod
[flutter_riverpod]: https://pub.dev/packages/flutter_riverpod
[flutter_hooks]: https://github.com/rrousselGit/flutter_hooks
