React hooks for Flutter. Hooks are a new kind of object that manages a Widget life-cycles. They are used to increase code sharing between widgets and as a complete replacement for StatefulWidget.

Overview

English | Português

Build codecov pub package pub package

Flutter Hooks

A Flutter implementation of React hooks: https://medium.com/@dan_abramov/making-sense-of-react-hooks-fdbde8803889

Hooks are a new kind of object that manages a Widget life-cycles. They exist for one reason: increase the code-sharing between widgets by removing duplicates.

Motivation

StatefulWidget suffers from a big problem: it is very difficult to reuse the logic of say initState or dispose. An obvious example is AnimationController:

class Example extends StatefulWidget {
  final Duration duration;

  const Example({Key key, required this.duration})
      : super(key: key);

  @override
  _ExampleState createState() => _ExampleState();
}

class _ExampleState extends State<Example> with SingleTickerProviderStateMixin {
  AnimationController? _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this, duration: widget.duration);
  }

  @override
  void didUpdateWidget(Example oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.duration != oldWidget.duration) {
      _controller!.duration = widget.duration;
    }
  }

  @override
  void dispose() {
    _controller!.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

All widgets that desire to use an AnimationController will have to reimplement almost all of this from scratch, which is of course undesired.

Dart mixins can partially solve this issue, but they suffer from other problems:

  • A given mixin can only be used once per class.
  • Mixins and the class shares the same object.
    This means that if two mixins define a variable under the same name, the result may vary between compilation fails to unknown behavior.

This library proposes a third solution:

class Example extends HookWidget {
  const Example({Key key, required this.duration})
      : super(key: key);

  final Duration duration;

  @override
  Widget build(BuildContext context) {
    final controller = useAnimationController(duration: duration);
    return Container();
  }
}

This code is strictly equivalent to the previous example. It still disposes the AnimationController and still updates its duration when Example.duration changes. But you're probably thinking:

Where did all the logic go?

That logic moved into useAnimationController, a function included directly in this library (see Existing hooks). It is what we call a Hook.

Hooks are a new kind of objects with some specificities:

  • They can only be used in the build method of a widget that mix-in Hooks.

  • The same hook is reusable an infinite number of times The following code defines two independent AnimationController, and they are correctly preserved when the widget rebuild.

    Widget build(BuildContext context) {
      final controller = useAnimationController();
      final controller2 = useAnimationController();
      return Container();
    }
  • Hooks are entirely independent of each other and from the widget.
    This means they can easily be extracted into a package and published on pub for others to use.

Principle

Similarly to State, hooks are stored on the Element of a Widget. But instead of having one State, the Element stores a List<Hook>. Then to use a Hook, one must call Hook.use.

The hook returned by use is based on the number of times it has been called. The first call returns the first hook; the second call returns the second hook, the third returns the third hook, ...

If this is still unclear, a naive implementation of hooks is the following:

class HookElement extends Element {
  List<HookState> _hooks;
  int _hookIndex;

  T use<T>(Hook<T> hook) => _hooks[_hookIndex++].build(this);

  @override
  performRebuild() {
    _hookIndex = 0;
    super.performRebuild();
  }
}

For more explanation of how they are implemented, here's a great article about how they did it in React: https://medium.com/@ryardley/react-hooks-not-magic-just-arrays-cd4f1857236e

Rules

Due to hooks being obtained from their index, some rules must be respected:

DO always prefix your hooks with use:

Widget build(BuildContext context) {
  // starts with `use`, good name
  useMyHook();
  // doesn't start with `use`, could confuse people into thinking that this isn't a hook
  myHook();
  // ....
}

DO call hooks unconditionally

Widget build(BuildContext context) {
  useMyHook();
  // ....
}

DON'T wrap use into a condition

Widget build(BuildContext context) {
  if (condition) {
    useMyHook();
  }
  // ....
}

About hot-reload

Since hooks are obtained from their index, one may think that hot-reload while refactoring will break the application.

But worry not, HookWidget overrides the default hot-reload behavior to work with hooks. Still, there are some situations in which the state of a Hook may get reset.

Consider the following list of hooks:

useA();
useB(0);
useC();

Then consider that after a hot-reload, we edited the parameter of HookB:

useA();
useB(42);
useC();

Here everything works fine; all hooks keep their states.

Now consider that we removed HookB. We now have:

useA();
useC();

In this situation, HookA keeps its state but HookC gets a hard reset. This happens because when a refactoring is done, all hooks after the first line impacted are disposed of. Since HookC was placed after HookB, it got disposed of.

How to use

There are two ways to create a hook:

  • A function

    Functions are by far the most common way to write a hook. Thanks to hooks being composable by nature, a function will be able to combine other hooks to create a custom hook. By convention, these functions will be prefixed by use.

    The following defines a custom hook that creates a variable and logs its value on the console whenever the value changes:

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

    When a hook becomes too complex, it is possible to convert it into a class that extends Hook, which can then be used using Hook.use.
    As a class, the hook will look very similar to a State and have access to life-cycles and methods such as initHook, dispose and setState It is usually a good practice to hide the class under a function as such:

    Result useMyHook(BuildContext context) {
      return use(const _TimeAlive());
    }

    The following defines a hook that prints the time a State has been alive.

    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();
      }
    }

Existing hooks

Flutter_hooks comes with a list of reusable hooks already provided.

They are divided into different kinds:

Primitives

A set of low-level hooks that interacts with the different life-cycles of a widget

name description
useEffect Useful for side-effects and optionally canceling them.
useState Create variable and subscribes to it.
useMemoized Cache the instance of a complex object.
useContext Obtain the BuildContext of the building HookWidget.
useValueChanged Watches a value and calls a callback whenever the value changed.

Object binding

This category of hooks allows manipulating existing Flutter/Dart objects with hooks. They will take care of creating/updating/disposing an object.

dart:async related:

name description
useStream Subscribes to a Stream and return its current state in an AsyncSnapshot.
useStreamController Creates a StreamController automatically disposed.
useFuture Subscribes to a Future and return its current state in an AsyncSnapshot.

Animation related:

name description
useSingleTickerProvider Creates a single usage TickerProvider.
useAnimationController Creates an AnimationController automatically disposed.
useAnimation Subscribes to an Animation and return its value.

Listenable related:

name description
useListenable Subscribes to a Listenable and mark the widget as needing build whenever the listener is called.
useValueNotifier Creates a ValueNotifier automatically disposed.
useValueListenable Subscribes to a ValueListenable and return its value.

Misc

A series of hooks with no particular theme.

name description
useReducer An alternative to useState for more complex states.
usePrevious Returns the previous argument called to [usePrevious].
useTextEditingController Create a TextEditingController
useFocusNode Create a FocusNode
useTabController Creates and disposes a TabController.
useScrollController Creates and disposes a ScrollController.
usePageController Creates and disposes a PageController.
useIsMounted An equivalent to State.mounted for hooks

Contributions

Contributions are welcomed!

If you feel that a hook is missing, feel free to open a pull-request.

For a custom-hook to be merged, you will need to do the following:

  • Describe the use-case.

    Open an issue explaining why we need this hook, how to use it, ... This is important as a hook will not get merged if the hook doens't appeal to a large number of people.

    If your hook is rejected, don't worry! A rejection doesn't mean that it won't be merged later in the future if more people shows an interest in it. In the mean-time, feel free to publish your hook as a package on https://pub.dev.

  • Write tests for your hook

    A hook will not be merged unles fully tested, to avoid breaking it inadvertendly in the future.

  • Add it to the Readme & write documentation for it.

Comments
  • Why no initState?

    Why no initState?

    I'm a bit confused why there is no equivalent of the initState fxn. If I had an animation and wanted it to play from 0 to 1, only once, when the widget is first created, how would I do that? I can see how you could use a custom isFirstBuild state value, but that seems kinda hacky compared to just having a dedicated init fxn. Am I missing something?

    wontfix 
    opened by esDotDev 48
  • Add flutter_mobx_hooks and add didBuild event on hook state

    Add flutter_mobx_hooks and add didBuild event on hook state

    fix https://github.com/rrousselGit/flutter_hooks/issues/225

    Just a preview to see if I'm on the right track. Once we agree on the feature I'll do the associated tests

    opened by jaumard 34
  • Dispose in reverse order

    Dispose in reverse order

    Currently, flutter_hooks disposes hooks from the top down, which is strange considering that Flutter itself disposes states from the bottom up. This causes exceptions when removing ChangeNotifier listeners that get cleaned up by useEffect's disposal callback.

    enhancement 
    opened by ds84182 29
  • useFuture/useStream: does initialData need to be required?

    useFuture/useStream: does initialData need to be required?

    At 0.16.0 with nullsafety useFuture gets required T initialData parameter: https://github.com/rrousselGit/flutter_hooks/blob/2b02b99ce6b327fd3a6df6f9c52cb2fe791600f6/lib/src/async.dart#L11-L15

    This means when there is no initial data, one have to explicitly pass null:

    final snapshot = useFuture(future, initialData: null);
    

    Are there any benefits of making it required?

    opened by PiN73 26
  • How to use it in a form?

    How to use it in a form?

    I'm trying to use hooks within my form, my approach looks like this:

    class MyWidget extends HookWidget {
      build(context) {
        final _formKey = useMemoized(() => GlobalKey<FormState>());
    
        final email = useState("");
        final password = useState("");
    
        _handleSubmit() {
          if (_formKey.currentState.validate()) {
            _formKey.currentState.save();
          }
        }
    
        return Form(
          key: _formKey,
          child: Flex(
            children: [
              TextFormField(onSaved: (value) => email.value = value),
              TextFormField(onSaved: (value) => password.value = value),
              Button(onPressed: _handleSubmit),
            ]
          )
        )
      }
    }
    

    The problem is, when I click submit, my email field becomes empty, but my password field remains unchanged, why is that? I'm pretty sure I'm missing something obvious here..

    I simplified my code a bit, so I may or may not have left out anything important, but this is the gist of it.

    question 
    opened by edisonywh 16
  • Example Gallery

    Example Gallery

    Hey hey -- I wanted to play with hooks and get a feel for them.

    I started creating a couple of one-off examples and my own hooks to internalize the concepts and ended up creating a kind of Gallery.

    I decided this might be a nice addition to the repo. It's incomplete, but I can add more examples and much more documentation if this looks like something you're interested in!

    Todo

    • [ ] Update README
    • [ ] Add documentation for each step of each example
    • [ ] Example Functional Hook
    • [ ] Example Class-based Hook
    • [ ] useMemoized
    • [ ] useStreamController & Friends
    • [ ] useAnimationController & Friends
    opened by brianegan 16
  • Destroying HookWidget?

    Destroying HookWidget?

    Am I doing something wrong? I keep getting odd memory leaks with my HookWidgets and I assume that I just need to clean things up when widget is destroyed, but there are no methods available.

    Here is some code:

    class StaticMapWidget extends HookWidget {
      Spot spot;
    
      _VM vm;
    
      StaticMapWidget(this.spot) {
        vm = _VM(spot);
      }
    }
    
    class _VM {
      Spot spot;
      VM(this.spot);
    
      //some helper methods
    }
    

    Am I allowed to do this with HookWidgets, passing parameters and having local instances of classes? Do I need to clean them up when widget is destroyed or unused references would be just GC-ed from memory?

    opened by AAverin 14
  • useEffect hook not working properly.

    useEffect hook not working properly.

    In React, useEffect hook works after first build. Bu in flutter_hooks, useEffect works before first render. That's why, make an operation with context object on useEffect(() { //operation using context }, []) makes following error: Cannot listen to inherited widgets inside HookState.initState. Use HookState.build instead.

    REACT HOOKS WORKING EXAMPLE Screen Shot 2020-07-12 at 13 58 40

    enhancement 
    opened by alirashid18 14
  • Q: Very curious if folks are finding this as useful as it sounds ...

    Q: Very curious if folks are finding this as useful as it sounds ...

    I love the hook stuff in react, and this looks very nice too ... just wary of the "sounds too good to be true" thing ... any thoughts from folks that have used this heavily?

    opened by aktxyz 14
  • Null safety migration

    Null safety migration

    General question since ValueNotifier has a non null generic i have implemented a second useState and useValueNotifier hook for nullable types: useNullableState and useNullableValueNotifier. Do you think this is the way to go or do you have a better idea? @rrousselGit

    I also just removed all tests that are no longer necessary because of null safety.

    opened by DevNico 13
  • why is the implementation of useEffect different from React hooks useEffect?

    why is the implementation of useEffect different from React hooks useEffect?

    documentation for useEffect on react hooks page

    The function passed to useEffect will run after the render is committed to the screen.

    Flutter hooks synchronously executes the function which will lead in a different result as expected (coming from react hooks).

    React also offers a hook called uselayouteffect that is more similar to flutter hooks useEffect.

    https://github.com/rrousselGit/flutter_hooks/issues/34 might be more similar to reacts useEffect.

    Kind of confusing 😩 Maybe @rrousselGit can clarify some things here :)

    opened by smiLLe 13
  • useEffect's disposer should be called before update

    useEffect's disposer should be called before update

    What I did and description

    I don't know whether it is a bug but I'll explain it first.

    I thought dispose function (printing unmount in the following code) should be called first after first mount. But it seems not.

    Is it intended?

    git clone -b effect_hook https://github.com/jeiea/flutter_example.git

    import 'package:flutter/material.dart';
    import 'package:flutter_hooks/flutter_hooks.dart';
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends HookWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: MyHomePage(),
        );
      }
    }
    
    class MyHomePage extends HookWidget {
      MyHomePage({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        final tapped = useState<bool?>(null);
        final logs = useState([]);
    
        useEffect(() {
          if (tapped.value == true) {
            // logs.value = [...logs.value, 'useEffect tap'];
            print('mount 1: ${logs.value}');
          } else if (tapped.value == false) {
            // logs.value = [...logs.value, 'useEffect tap 2'];
            print('mount 2: ${logs.value}');
          }
          return () {
            // logs.value = [...logs.value, 'useEffect unmount'];
            print('unmount: ${logs.value}');
          };
        }, [tapped.value != true]);
    
        return Scaffold(
          body: SafeArea(
            child: Padding(
              padding: const EdgeInsets.all(16.0),
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  SizedBox(
                    width: double.infinity,
                    child: ElevatedButton(
                      onPressed: () {
                        print('tap');
                        tapped.value = tapped.value != true;
                      },
                      child: Text('Tap me'),
                    ),
                  ),
                  Expanded(
                    child: ListView.builder(
                      itemCount: logs.value.length,
                      itemBuilder: ((context, index) => Text(logs.value[index])),
                    ),
                  ),
                ],
              ),
            ),
          ),
        );
      }
    }
    
    

    Expected behavior

    flutter: mount 2: []
    flutter: tap
    flutter: unmount: []
    flutter: mount 1: []
    flutter: tap
    flutter: unmount: []
    flutter: mount 2: []
    

    Actual behavior

    flutter: mount 2: []
    flutter: tap
    flutter: mount 1: []
    flutter: unmount: []
    flutter: tap
    flutter: mount 2: []
    flutter: unmount: []
    
    enhancement 
    opened by jeiea 4
  • hook to accept events, eg: useDebounce

    hook to accept events, eg: useDebounce

    dear all,

    but now, my solution is to return a function, which whenever called, will send event, eg: debounce event

    and i think the is not the best solution, any suggestion?

    thanks

    documentation needs triage 
    opened by peterweb2005 0
  • Trigger a callback when value listenable changes

    Trigger a callback when value listenable changes

    Is your feature request related to a problem? Please describe. Having a big and complicated screen, that is optimised using useValueNotifier instead of useState, often there is a need to do something when value notifier changes. Using useValueListenable would trigger rebuild, which is unwanted due to rebuilds optimisation

    Describe the solution you'd like There is a way to add and remove listener to value notifier. This could be utilised to build a useValueListenableChanged hook, similar to useValueChanged hook that is present already.

    Describe alternatives you've considered useValueChanged hook works on a regular variable, not on valueListenable

    Additional context Here is what seemed to have worked. If there is no negative feedback I can make a PR:

    import 'package:flutter/foundation.dart';
    import 'package:flutter/src/widgets/framework.dart';
    import 'package:flutter_hooks/flutter_hooks.dart';
    
    useValueListenableChanged<T>(
        ValueListenable<T> listenable, Function(T value) valueListenableChanged) {
      return use(
          _UseValueListenableChangedHook(listenable, valueListenableChanged));
    }
    
    class _UseValueListenableChangedHook<T> extends Hook {
      const _UseValueListenableChangedHook(
          this.valueListenable, this.valueListenableChanged);
    
      final Function(T value) valueListenableChanged;
      final ValueListenable<T> valueListenable;
    
      @override
      _ValueListenableChangedState createState() =>
          _ValueListenableChangedState<T>();
    }
    
    class _ValueListenableChangedState<T>
        extends HookState<dynamic, _UseValueListenableChangedHook<T>> {
      _ValueListenableChangedState();
    
      late VoidCallback _valueListenableChanged;
    
      @override
      void initHook() {
        _valueListenableChanged = () {
          hook.valueListenableChanged(hook.valueListenable.value);
        };
        hook.valueListenable.addListener(_valueListenableChanged);
      }
    
      @override
      void dispose() {
        hook.valueListenable.removeListener(_valueListenableChanged);
      }
    
      @override
      build(BuildContext context) {}
    
      @override
      String get debugLabel => 'useValueListenableChanged';
    
      @override
      Object? get debugValue => hook.valueListenable;
    }
    
    
    enhancement needs triage 
    opened by AAverin 0
  • Documentation of useTextEditingController with useEffect needs to be updated

    Documentation of useTextEditingController with useEffect needs to be updated

    Hello,

    The doc of useTextEditingController suggests using useEffect to update the TextEditingController.text whenever a provided ValueListenable changes.

    But, When the provided ValueListenable updates the TextEditingController.text for the second time it'll throw an exception: setState() or markNeedsBuild() called during build

    Suggestion: Delaying the update by a frame using Future.microtask inside useEffect fixes the issue.

    documentation 
    opened by AhmedLSayed9 0
  • FutureProvider ref.watch automatically calling itself

    FutureProvider ref.watch automatically calling itself

    Describe the bug FutureProvider ref.watch automatically calling itself

    To Reproduce

    Check the screenshot

    Expected behavior wait until it's get called

    Screenshot 2022-08-16 at 12 54 27 PM bug needs triage 
    opened by swarupbhc 1
Owner
Remi Rousselet
Flutter enthusiast. You'll find me on stackoverflow. Or as a speaker in Flutter meetups
Remi Rousselet
An extensive snap tool/widget for Flutter that allows very flexible snap management and snapping between your widgets.

An extensive snap tool/widget for Flutter that allows very flexible snap management and snapping between your widgets.

AliYigitBireroglu 101 Dec 16, 2022
📓 Storyboard your components with Flutterbook. Develop, document, & test any kind of Flutter component.

Flutterbook A storyboarding tool to accelerate the development of Flutter widgets. Inspired by Storybook.js. The package is built to support Windows a

Philip Vu 25 Oct 7, 2022
Flutter ScrollView Observer - a library of widget that can be used to listen for child widgets those are being displayed in the scroll view

Flutter ScrollView Observer - a library of widget that can be used to listen for child widgets those are being displayed in the scroll view

林洵锋 67 Jan 6, 2023
A flutter package which provides most commonly used widgets with their normal and neon version

This is a flutter package which provides most commonly used widgets with their normal and neon version. There are multiple different types of widgets under this package, which can be used to create more neon theme widget

ojas 24 Oct 7, 2022
A new flutter package for collection of common popular social media widgets

Social Media Widgets - package A new flutter package for collection of common popular social media widgets Currently available widgets Snapchat screen

theboringdeveloper 34 Nov 12, 2022
Custom widgets and utils using Flutter framework widgets and Dart language

reuse_widgets_and_utils The custom widgets and utils using Flutter framework widgets and Dart programming language. Getting Started This project is a

null 1 Oct 29, 2021
A sliding up panel widget which can be used to show or hide content, beautiful and simple.

flutter_sliding_up_panel A sliding up panel widget which can be used to show or hide content, beautiful and simple. demo Getting Started dependencies:

null 25 Dec 12, 2022
Flutter package: Assorted layout widgets that boldly go where no native Flutter widgets have gone before.

assorted_layout_widgets I will slowly but surely add interesting widgets, classes and methods to this package. Despite the package name, they are not

Marcelo Glasberg 122 Dec 22, 2022
Flutter-useful-widgets - Flutter Useful Widgets

useful_widgets This package makes it easy to build apps by providing a list of simple and useful widgets. import 'package:useful_widgets/useful_widget

Ricardo Crescenti 6 Jun 20, 2022
Widgets beginner - Widgets beginner with flutter

Widgets beginner - Widgets beginner with flutter

Tukhtamurodov Sardorbek 2 Feb 6, 2022
Global loading widget, which can be used through simple configuration.

load Global loading widget, which can be used through simple configuration. Pure flutter library, not use native code. It is similar to OKToast in use

Caijinglong 35 Nov 4, 2022
Powerful Complete and Beautiful Search Component for Flutter

A highly customizable search component to accelerate your development. Overview There are many search or search components for Flutter, however this o

Tiagosito 31 Jul 27, 2022
RoundedLoadingButton is a Flutter package with a simple implementation of an animated loading button, complete with success and error animations.

rounded_loading_button RoundedLoadingButton is a Flutter package with a simple implementation of an animated loading button, complete with success and

Chris Edgington 223 Jan 4, 2023
Code generation for Flutter Padding widgets based on your constants

Code generation for Flutter Padding widgets based on your constants

Emanuele 14 Oct 20, 2022
A code generator to write widgets as function without loosing the benefits of classes.

Widgets are cool. But classes are quite verbose: class Foo extends StatelessWidget { final int value; final int value2; const Foo({Key key, thi

Remi Rousselet 528 Dec 29, 2022
The complete solution for Dart command-line interfaces

The complete solution for Dart command-line interfaces, inspired by node commander.js which created by tj.

Axetroy 6 Feb 21, 2020
🔍 Code generation for selectors of class fields that helps reduce repetitive code

Code generation for selectors of class fields and enum cases that helps reduce repetitive code.

Yakov K. 7 Oct 3, 2022
A flutter widget for comparing two stacked widgets by dragging a slider thumb to reveal either sides of the slider horizontally or vertically.

Juxtapose A flutter widget for comparing two stacked widgets by dragging a slider thumb to reveal either sides of the slider horizontally or verticall

Leslie Arkorful 74 Nov 24, 2022
Responsive Widgets Prefix allows you to add the "Responsive" prefix to any widget that needs scaling or font size increases

Responsive Widgets Prefix allows you to add the Responsive prefix to any widget that needs scaling or font size increases (for varying device screen sizes).

The Mobile Applications Community 2 Apr 18, 2022