Flutter Hooks

repository·master·Indexed 25 days ago

https://github.com/rrousselgit/flutter_hooks

A library that brings React Hooks concepts to Flutter, allowing developers to manage widget lifecycles and reuse logic via HookWidget. It provides primitive hooks like useState and useEffect, animation hooks, listenable hooks, and utility hooks for controllers such as TextEditingController and ScrollController. The library enables the creation of custom hooks through functions or by extending the Hook class to reduce StatefulWidget boilerplate.

Tokens
5K
Snippets
7
Records
32
Agent score
84%

What's inside flutter_hooks

  1. Overview of Flutter Hooks

    master
    Flutter Hooks is an implementation of React Hooks for Flutter. It provides a new way to manage Widget lifecycles, reducing boilerplate code and increasing component reusability. Instead of using StatefulWidget and manually managing initState, didUpdateWidget, and dispose (e.g., for an AnimationController), you can use HookWidget and built-in hooks like useAnimationController to handle these lifecycle events automatically.
  2. Understand Flutter Hooks and HookWidget

    master

    Flutter Hooks is a library that provides a new way to manage widget lifecycles, reducing code duplication by replacing StatefulWidget boilerplate with reusable hooks. Instead of using StatefulWidget, you use HookWidget and call hooks within the build method.

    Key characteristics:

    • Hooks can only be used inside the build method of a HookWidget.
    • The same hook can be reused multiple times within the same widget, with each instance maintaining its own independent state.
    • Hooks are independent of widgets, making them easy to extract into separate packages.
    class Example extends HookWidget {
      const Example({super.key, required this.duration});
    
      final Duration duration;
    
      @override
      Widget build(BuildContext context) {
        final controller = useAnimationController(duration: duration);
        return Container();
      }
    }
  3. Understand Flutter Hooks principles and motivation

    master

    Flutter Hooks provides a way to manage a Widget lifecycle and share logic between widgets without the boilerplate of StatefulWidget. Instead of manually implementing initState, didUpdateWidget, and dispose, you use hooks within a HookWidget.

    Key Concepts:

    • Hooks are objects stored in the Element of a Widget in a List<Hook>.
    • Ordering matters: Hooks are retrieved by their index based on the order they are called during the build method. The first call returns the first hook, the second returns the second, and so on.
    • Independence: Hooks are independent of each other and the widget, making them easily extractable into separate packages.
  4. Create a complex custom hook using a class

    master

    For highly complex logic, you can create a hook by extending the Hook class. This allows access to lifecycle methods like initHook, dispose, and setState. It is a best practice to wrap this class inside a function to hide the implementation details.

    Example of a class-based hook that tracks time alive:

    // The public API function
    Result useMyHook(BuildContext context) {
      return use(const _TimeAlive());
    }
    
    // The internal implementation
    class _TimeAlive extends Hook<void> {
      const _TimeAlive();
    
      @override
      _TimeAliveState createState() => _TimeAliveState();
    }
    
    class _TimeAliveState extends HookState<void, _TimeAlive> {
      DateTime start;
    
      @override
      void initHook() {
        super.initHook();
        start = DateTime.now();
      }
    
      @override
      void build(BuildContext context) {}
    
      @override
      void dispose() {
        print(DateTime.now().difference(start));
        super.dispose();
      }
    }
  5. Rules for using hooks

    master

    Because hooks are retrieved based on their call index within the build method, you must follow these rules to ensure state is preserved correctly:

    1. Prefix with use: Always name your hook functions starting with use (e.g., useMyHook()) to clearly identify them.
    2. Call hooks unconditionally: Hooks must be called in the same order every time the build method runs.
    3. Do not wrap hooks in conditions: Never place a hook inside an if statement or any other conditional logic. This changes the index of subsequent hooks and will break the application state.
  6. Follow the rules for using hooks

    master

    Because hooks are stored in a list and accessed by index, you must follow these rules to ensure state is preserved correctly:

    1. Prefix names with use: Always start hook function names with use (e.g., useMyHook()) to signal to other developers that it is a hook.
    2. Call hooks unconditionally: Hooks must be called at the top level of your build method.
    3. Do not wrap hooks in conditionals: Never place a hook inside an if statement or any other conditional logic. This ensures the order of hook calls remains consistent across rebuilds.
  7. Handle Hot-Reload behavior with hooks

    master

    Because hooks are retrieved by index, refactoring the order of hooks can reset state.

    • Safe Refactoring: Changing the parameters of an existing hook (e.g., changing useB(0) to useB(42)) will preserve the hook's state.
    • Breaking Changes: If you remove a hook from the list, all hooks appearing after the removed hook in the code will have their indices shifted and their state will be reset.

    Example: If you have useA(), useB(), and useC(), and you remove useB(), useC() will now be at the index previously occupied by useB(), causing its state to be reset.

  8. Understand Hook state during Hot Reload

    master

    Hooks are preserved by their index in the Element's hook list. During hot reload:

    • Modifying arguments of an existing hook (e.g., changing useB(0) to useB(42)) will preserve the state of all hooks.
    • Deleting a hook will cause all subsequent hooks in the list to be disposed and reset. For example, if you have useA(), useB(), and useC(), and you remove useB(), useC() will be forced to reset because its index has shifted.
  9. Create a custom hook using a function

    master

    The most common way to create a custom hook is by writing a function. Functions allow you to compose existing hooks to create more complex logic. By convention, these functions must start with the use prefix.

    Example of a custom hook that logs a value whenever it changes:

    ValueNotifier<T> useLoggedState<T>([T initialData]) {
      final result = useState<T>(initialData);
      useValueChanged(result.value, (_, __) {
        print(result.value);
      });
      return result;
    }
  10. Create a custom hook using a class

    master

    For complex hooks, you can extend the Hook class. This approach provides access to lifecycle methods similar to a State class, such as initHook, dispose, and setState.

    A common pattern is to hide the class inside a function that calls use().

    Example of a hook that prints the time elapsed since it was created:

    Result useMyHook() {
      return use(const _TimeAlive());
    }
    
    class _TimeAlive extends Hook<void> {
      const _TimeAlive();
    
      @override
      _TimeAliveState createState() => _TimeAliveState();
    }
    
    class _TimeAliveState extends HookState<void, _TimeAlive> {
      DateTime start;
    
      @override
      void initHook() {
        super.initHook();
        start = DateTime.now();
      }
    
      @override
      void build(BuildContext context) {}
    
      @override
      void dispose() {
        print(DateTime.now().difference(start));
        super.dispose();
      }
    }
  11. Create a functional custom hook

    master

    The most common way to create a custom hook is by defining a function. Because hooks can be composed, a single function can combine multiple existing hooks into a complex custom hook. By convention, these functions should be prefixed with use.

    ValueNotifier<T> useLoggedState<T>([T initialData]) {
      final result = useState<T>(initialData);
      useValueChanged(result.value, (_, __) {
        print(result.value);
      });
      return result;
    }