# Flutter Hooks, everything to know about them

Flutter hooks is a powerful library that brings the functionality of [React Hooks](https://dev.to/dan_abramov/making-sense-of-react-hooks-2eib) to Flutter.

Why should we use Hooks?

* **Simplified Code:** Hooks allow us to <mark>write more readable code by reducing boilerplate.</mark>
    
* **Reusable Logic:** Hooks enable us to extract and reuse stateful logic across different components.
    
* **Lifecycle Management:** Hooks help in managing the lifecycle of stateful objects like controllers <mark>without needing a StatefulWidget</mark>.
    

In here we are going to 👀 a tutorial that'll help us learn Flutter Hooks.

As always all of the code for the project will be present in the [learn\_flutter\_hooks](https://github.com/khkred/learn_flutter_hooks) repository.

# Basic Concepts:

In order to use Flutter hooks let's learn the basic concepts

1. **HookWidget**: This is a replacement of `StatefulWidget` when using hooks.
    
2. **useState**: Manages state within a HookWidget.
    
3. **useEffect:** Executes a function in response to lifecycle events.
    
4. **useMemoized:** Memorizes a value to avoid recomputing it on every build.
    
5. **useTextEditingController:** Manages a TextEditingController.
    

## Basic `HookWidget` with `useState`

### Setting up Flutter Hooks.

**Add the Flutter Hooks package to your**`pubspec.yaml`:

```bash
flutter pub add flutter_hooks
```

Let's create a counter Screen to demonstrate the example of `useState`:

```dart
class CounterPage extends HookWidget {
  const CounterPage({super.key});

  @override
  Widget build(BuildContext context) {
    // Use a hook to manage CounterState
    final counter = useState(0);

    return Scaffold(
      appBar: AppBar(
        title: const Text('Counter using Hooks'),
      ),
      body: Center(
        child: Text(
          'Counter: ${counter.value}',
          style: const TextStyle(fontSize: 24),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
        // We are adding the value of counter here
          counter.value++;
        },
        child: const Icon(Icons.add),
      ),
    );
  }
}
```

Obviously since we are using the `useState()` the page works as intended.

![useState in Hooks](https://media1.giphy.com/media/v1.Y2lkPTc5MGI3NjExeWF1dXM5MG5iZHJrZ3RnbzYyMGF0bzMydmhxOGhnOWVucXBqZnF5YyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/S0p5h4sgUibjtY76oM/giphy.gif align="center")

Now in the above tutorial we built two things:

1. **HookWidget**: `CounterScreen` extends `HookWidget` instead of `StatefulWidget`.
    
2. **useState**: <mark>The </mark> `useState` <mark> hook is used to declare a state variable </mark> `counter` <mark> and its updater </mark> `counter.value`<mark>.</mark>
    

You can find the code for the above tutorial here: [basic\_hooks\_concepts](https://github.com/khkred/learn_flutter_hooks/tree/basic_hooks_concepts)

In the next tutorial let's use hooks for a `TextEditingController`

# Tutorial: Build a Login Page using Flutter Hooks

## Build the LoginPage using `StatefulWidget`

First let's build a sign in form using Stateful Widget. Now since we are going to use the `TextEditingController` . Let's look at all the fields we'll have to do.

### Build a Basic TextField UI to enter an email

**Login Page:**

First I am creating a Stateful Login Page without widgets as of yet.

```dart
class LoginPage extends StatefulWidget {
  const LoginPage({super.key});

  @override
  State<LoginPage> createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold();
  }
}
```

**Add Email**`TextField` :

```dart

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: const Text('Stateful Login Page'),
        ),
        body: Center(
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: Form(
              child: TextFormField(
                decoration: InputDecoration(
                  border: const OutlineInputBorder(),
                  suffixIcon: IconButton(
                      onPressed: () {
                        // We'll implement this later
                      },
                      icon: const Icon(Icons.arrow_forward)),
                  labelText: 'Enter your email',
                ),
              ),
            ),
          ),
        ));
  }
```

### Add a `TextEditingController` to our LoginPage:

Now that we have the email let's add a `TextEditingController` named `emailController`

**initialization:**

```dart
final _formKey = GlobalKey<FormState>();
final TextEditingController _emailController = TextEditingController();
```

**dispose:**

```dart
 @override
  void dispose() {
    _emailController.dispose();
    super.dispose();
  }
```

**signIn() method:**

```dart
void signIn() {
    if (_formKey.currentState!.validate()) {
      final email = _emailController.text;
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: Text('Email: $email'),
        ),
      );
    }
  }
```

**Validator:**

Finally let's add the validator and connect the `signIn()` to the suffix Button.

```dart
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: const Text('Stateful Login Page'),
        ),
        body: Center(
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: Form(
              key: _formKey,
              child: TextFormField(
                controller: _emailController,
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'Please Enter your email';
                  }
                  if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(value)) {
                    return 'Please enter a valid email address';
                  }
                  return null;
                },
                decoration: InputDecoration(
                  border: const OutlineInputBorder(),
                  suffixIcon: IconButton(
                      onPressed: signIn, icon: const Icon(Icons.arrow_forward)),
                  labelText: 'Enter your email',
                ),
              ),
            ),
          ),
        ));
  }
```

Perfect. Now when we run the app. We are going to get the following output:

![Using Stateful Widget](https://media0.giphy.com/media/v1.Y2lkPTc5MGI3NjExdm9jNGpxNWlmeWx0NGpibzl1MW0wenlhenJhZDRicWRpc3U0bWo0biZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/7LwPFGauwlJy1J7Hsh/giphy.gif align="center")

You can find the code for the above Stateful Widget here: [stateful\_sign\_in](https://github.com/khkred/learn_flutter_hooks/tree/stateful_sign_in)

## Build the LoginPage using `FlutterHooks`

Before we being there are few things we must learn about hooks:

* Hooks can only be used in the build() function of our widgets as we will soon see in the tutorial.
    
* We are going to use a `HookWidget` instead of `Stateful` or `Stateless` Widgets.
    

So let's create a new page

### **LoginHooksPage:**

**Basic Page:**

```dart
class LoginHooksPage extends HookWidget {
  const LoginHooksPage({super.key});
  @override
  Widget build(BuildContext context) {
    return Scaffold();
  }
}
```

Now let's initialize the `_formKey` and `_emailController` :

**initialization:**

Remember we can <mark>use hooks inside the build()</mark>:

```dart
@override
  Widget build(BuildContext context) {
    final _formKey = useMemoized(() => GlobalKey<FormState>());
    final _emailController = useTextEditingController();

    return Scaffold();
  }
```

Here's the entire LoginHooksPage:

```dart
class LoginHooksPage extends HookWidget {
  const LoginHooksPage({super.key});

  @override
  Widget build(BuildContext context) {
    final _formKey = useMemoized(() => GlobalKey<FormState>());
    final _emailController = useTextEditingController();

    void _signIn() {
      if (_formKey.currentState!.validate()) {
        final email = _emailController.text;

        // Show the email in a SnackBar
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('Email: $email'),
          ),
        );
      }
    }

    return Scaffold(
      appBar: AppBar(
        title: const Text('Hooks Login Page'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Form(
          child: TextFormField(
            controller: _emailController,
            decoration: InputDecoration(
              labelText: 'Email',
              suffixIcon: IconButton(
                onPressed: _signIn,
                icon: const Icon(Icons.arrow_forward),
              ),
            ),
            validator: (value) {
              if (value == null || value.isEmpty) {
                return 'Please enter your email';
              }
              if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(value)) {
                return 'Please enter a valid email address';
              }
              return null;
            },
          ),
        ),
      ),
    );
  }
}
```

As we can see it works perfectly:

![hooks email](https://media1.giphy.com/media/v1.Y2lkPTc5MGI3NjExZW84d3Z2ZGZzYTU5dzY1cHJhMndteTVyZzBuN2YwcGJyZjJkbDRyciZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/UIuMfGGhu8xEg1foxF/giphy.gif align="center")

Perfect, so in the above tutorial. We have used

1. **useMemoized**: Used to create and memoize the `GlobalKey<FormState>()`.
    
2. **useTextEditingController**: Used to create and manage the `TextEditingController`.
    

You can find code for the above tutorial in the following branch: [hooks\_sign\_in](https://github.com/khkred/learn_flutter_hooks/tree/hooks_sign_in)

## Tutorial : Learning `useEffect`

Now let's dive into `useEffect` hook, which is used to perform side effects in a `HookWidget` .

### **What is**`useEffect`**?**

* The useEffect hooks allows us to run side-effectful code based on changes to state or props.
    
* It's similar to the lifecycle methods `initState`, `didUpdateWidget`, `dispose` in a StatefulWidget
    

### Basic Usage:

The `useEffect` hook has two parameters:

1. A function that contains the side-effect code.
    
2. A list of dependences that determine when the side-effect function should run.
    

### Example 1: Simple `useEffect`

Let's start with a simple example where we log a message to the console whenever the component is rendered.

```dart
class ExampleScreen extends HookWidget {
  const ExampleScreen({super.key});

  @override
  Widget build(BuildContext context) {
    //Using useEffect to log a message on render
    useEffect(() {
      print('ExampleScreen rendered');
      return null; // No cleanup needed
    }, []);

    return Scaffold(
      appBar: AppBar(
        title: const Text('useEffect Example'),
      ),
      body: const Center(
        child: Text('Check the console for messages'),
      ),
    );
  }
}
```

So in the above screen: The effect function runs after the first render and every time the dependencies change.

In the above example :

1. Since we are tracking an empty list of dependencies `[]`, useEffect() was only called once.
    
2. But if we are not tracking any dependencies. It would have been called every time.
    

You can see the example here:

%[https://youtu.be/7rdjX4vYuDk] 

### Example 2: `useEffect` with Dependencies

Now let's create an example where `useEffect` depends on a state variable. we'll update the counter and use `useEffect` to log the counter value whenever it changes.

```dart
class CounterUEPage extends HookWidget {
  const CounterUEPage({super.key});

  @override
  Widget build(BuildContext context) {
    final counter = useState(0);

    useEffect(() {
      print('Counter Value: ${counter.value}');
      return null;
    }, [counter.value]);
    return Scaffold(
      appBar: AppBar(
        title: const Text('Counter using useEffect'),
      ),
      body: Center(
        child: Text(
          'Counter: ${counter.value}',
          style: const TextStyle(fontSize: 24),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => counter.value++,
        child: const Icon(Icons.add),
      ),
    );
  }
}
```

So in the above example we are doing two things:

1. **useState**: Manages the `counter` state.
    
2. **useEffect**: Runs the effect function whenever the `counter.value` changes. In this case, it logs the new counter value to the console.
    

**Important: What is the point of a dependency aka Key ?**

Now let's take a look at the following code:

```dart
useEffect(() {
      print('Counter Value: ${counter.value}');
      return null;
    }, [counter.value]);
```

<mark>In here the reason we suggested [counter.value] as a Key is because we don't want </mark> `useEffect()` <mark> to be called every time there is a hot reload. Instead we only want </mark> `useEffect()` <mark> to be called when there is a change in the value of the counter.</mark>

### Example 3: `useEffect` with Cleanup

* Sometimes you need to cleanup after a side effect, like unsubscribing from a stream or cleaning a timer.
    
* The useEffect hook can return a cleanup function.
    

Let's create a new file named `TimerUEPage` to show useEffect cleanup with a timer

```dart
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';

class TimerUEPage extends HookWidget {
  const TimerUEPage({super.key});

  @override
  Widget build(BuildContext context) {
    final time = useState(0);

    // Using useEffect to start a timer and clean it up when the component is unmounted
    useEffect(() {
      final timer = Timer.periodic(const Duration(seconds: 1), (timer) {
        time.value++;
      });

      return () => timer.cancel(); // Cleanup function to cancel the timer
    });
    return Scaffold(
      appBar: AppBar(
        title: const Text('useEffect Example with Cleanup'),
      ),
      body: Center(
        child: Text(
          'Time: ${time.value} seconds',
          style: const TextStyle(fontSize: 24),
        ),
      ),
    );
  }
}
```

These are the two main concepts of the above page:

1. **useState**: Manages the `time` state.
    
2. **useEffect**: Starts a timer that increments the `time` value every second. The cleanup function cancels the timer when the component is unmounted.
    

As you can see it works:

![Cleanup using useEffect](https://media1.giphy.com/media/v1.Y2lkPTc5MGI3NjExcTRwOXoxc2F4cG94YzBtOWQyb3lqOGwzN3E2cnFpbm9lenZhdTNndCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/vpNpe4M3kfSQZb1GS4/giphy.gif align="center")

### Example 4: Applying `useEffect` in SignIn Page

Now let's apply `useEffect` to the sign-in form example to demonstrate a side effect, such as logging the email value when it changes.

Let's create a page named `LoginUEPage`

```dart
class LoginUEPage extends StatelessWidget {
  const LoginUEPage({super.key});

  @override
  Widget build(BuildContext context) {
    final _formKey = useMemoized(() => GlobalKey<FormState>());
    final _emailController = useTextEditingController();

    useEffect(() {
      print('Email: ${_emailController.text}');
      return null; // No Cleanup needed
    }, [_emailController.text]);

    void _signIn(){
................
}
}
```

Now in this scenario:  
\- **useEffect**: Logs the email value whenever it changes by including `_emailController.text` in the dependencies list.

This covers the basics of `useEffect`. You can use it to handle various side effects in your application, making your code cleaner and more manageable.

You can find the code for useEffect in here: [hooks\_useEffect](https://github.com/khkred/learn_flutter_hooks/tree/hooks_useEffect)

### Source Code:

You can find the total source code of the app here: [learn\_flutter\_hooks](https://github.com/khkred/learn_flutter_hooks)
