Convert Figma logo to code with AI

rrousselGit logoflutter_hooks

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.

3,108
176
3,108
24

Top Related Projects

41,752

React Hooks — 👍

13,985

A high-quality & reliable React Hooks library. https://ahooks.pages.dev/

1,906

React hooks done right, for browser and SSR.

Awesome React Hooks

42,148

🤖 Powerful asynchronous state management, server-state utilities and data fetching for the web. TS/JS, React Query, Solid Query, Svelte Query and Vue Query.

Quick Overview

The flutter_hooks GitHub repository is a library that provides a set of reusable hooks for Flutter, allowing developers to create more modular and composable widgets. It aims to simplify the management of state and lifecycle in Flutter applications.

Pros

  • Reusability: The hooks provided by the library can be easily reused across different parts of the application, promoting code reuse and maintainability.
  • Composability: Hooks can be combined and composed together, enabling the creation of more complex and flexible widget behaviors.
  • Separation of Concerns: Hooks help separate the concerns of state management and UI, making the codebase more organized and easier to reason about.
  • Testability: Hooks can be tested in isolation, improving the overall testability of the application.

Cons

  • Learning Curve: Developers new to the concept of hooks may need to invest time in understanding how they work and how to effectively use them.
  • Potential Overhead: The use of hooks can add a small amount of overhead to the application, which may be a concern for performance-sensitive use cases.
  • Ecosystem Integration: While the library is well-maintained, it may not have the same level of ecosystem integration and community support as some of the more widely-used state management solutions in the Flutter ecosystem.
  • Dependency on Flutter: The flutter_hooks library is tightly coupled with the Flutter framework, and its usage is limited to Flutter-based applications.

Code Examples

Here are a few examples of how to use the flutter_hooks library:

  1. Using the useState hook:
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) {
    final count = useState(0);

    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Counter App'),
        ),
        body: Center(
          child: Text('You have pressed the button ${count.value} times.'),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () => count.value++,
          child: Icon(Icons.add),
        ),
      ),
    );
  }
}

This example demonstrates the use of the useState hook to manage the state of a simple counter application.

  1. Using the useEffect hook:
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) {
    final count = useState(0);

    useEffect(() {
      print('The count is now: ${count.value}');
      return null;
    }, [count.value]);

    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Counter App'),
        ),
        body: Center(
          child: Text('You have pressed the button ${count.value} times.'),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () => count.value++,
          child: Icon(Icons.add),
        ),
      ),
    );
  }
}

This example demonstrates the use of the useEffect hook to log the current value of the counter whenever it changes.

  1. Using the useReducer hook:
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) {
    final state = useReducer(
      (state, action) {
        switch (action) {
          case 'increment':
            return state + 1;
          case 'decrement':
            return state - 1;
          default:
            return state;
        }
      },
      0,
    );

    return MaterialApp

Competitor Comparisons

41,752

React Hooks — 👍

Pros of react-use

  • Larger collection of hooks (100+) covering a wide range of use cases
  • More mature project with a larger community and more frequent updates
  • Includes hooks for both browser and server-side rendering

Cons of react-use

  • Potentially larger bundle size due to the extensive collection of hooks
  • Some hooks may be less optimized compared to custom implementations
  • Learning curve can be steeper due to the large number of available hooks

Code Comparison

react-use:

import { useToggle } from 'react-use';

const [isOn, toggleIsOn] = useToggle(false);

flutter_hooks:

import 'package:flutter_hooks/flutter_hooks.dart';

final isOn = useState(false);

Additional Notes

Both libraries aim to provide reusable logic through hooks, but react-use is specifically for React applications, while flutter_hooks is for Flutter projects. react-use offers a more extensive collection of pre-built hooks, while flutter_hooks provides a foundation for creating custom hooks in Flutter. The choice between them depends on the specific framework and project requirements.

13,985

A high-quality & reliable React Hooks library. https://ahooks.pages.dev/

Pros of hooks

  • Broader scope: Supports React, Vue, and other frameworks, not limited to Flutter
  • Larger community: More contributors and users due to its multi-framework support
  • More extensive hook collection: Offers a wider variety of pre-built hooks for common use cases

Cons of hooks

  • Less Flutter-specific: May not fully leverage Flutter's unique features and patterns
  • Potential performance overhead: Generic hooks might not be as optimized for Flutter as flutter_hooks
  • Learning curve: Developers familiar with Flutter may need to adapt to a different API style

Code Comparison

flutter_hooks:

final counter = useState(0);
final name = useTextEditingController();
useEffect(() {
  print('Name changed: ${name.text}');
  return null;
}, [name]);

hooks:

const [count, setCount] = useState(0);
const [name, setName] = useState('');
useEffect(() => {
  console.log(`Name changed: ${name}`);
}, [name]);

While both libraries provide similar functionality, flutter_hooks is tailored specifically for Flutter, using Dart syntax and Flutter-specific types. hooks, on the other hand, uses a more generic JavaScript/TypeScript approach that can be applied across different frameworks.

1,906

React hooks done right, for browser and SSR.

Pros of react-hookz/web

  • Broader ecosystem: React has a larger community and more resources available
  • More hooks: Offers a wider variety of pre-built hooks for common use cases
  • TypeScript support: Built with TypeScript, providing better type safety

Cons of react-hookz/web

  • Limited to web development: Unlike Flutter Hooks, it's not cross-platform
  • Learning curve: May require more React-specific knowledge to use effectively
  • Performance: React's virtual DOM can be slower than Flutter's direct rendering

Code Comparison

react-hookz/web:

import { useBoolean } from '@react-hookz/web';

function Component() {
  const [value, toggle] = useBoolean(false);
  return <button onClick={toggle}>{value ? 'ON' : 'OFF'}</button>;
}

flutter_hooks:

import 'package:flutter_hooks/flutter_hooks.dart';

Widget build(BuildContext context) {
  final value = useState(false);
  return ElevatedButton(
    onPressed: () => value.value = !value.value,
    child: Text(value.value ? 'ON' : 'OFF'),
  );
}

Both libraries aim to simplify state management in their respective frameworks. react-hookz/web provides a more extensive set of hooks for React applications, while flutter_hooks brings the concept of hooks to Flutter development. The choice between them largely depends on the target platform and the developer's familiarity with React or Flutter ecosystems.

Awesome React Hooks

Pros of awesome-react-hooks

  • Extensive collection of community-contributed React hooks
  • Regularly updated with new hooks and resources
  • Serves as a comprehensive reference for React developers

Cons of awesome-react-hooks

  • Not a standalone library, requires additional implementation
  • May include hooks of varying quality or maintenance levels
  • Lacks the tight integration with a specific framework like Flutter

Code Comparison

React Hook (awesome-react-hooks):

import { useState, useEffect } from 'react';

const useWindowSize = () => {
  const [size, setSize] = useState([window.innerWidth, window.innerHeight]);
  useEffect(() => {
    const handleResize = () => setSize([window.innerWidth, window.innerHeight]);
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);
  return size;
};

Flutter Hook (flutter_hooks):

import 'package:flutter_hooks/flutter_hooks.dart';

Size useWindowSize() {
  final size = useState(MediaQuery.of(context).size);
  useEffect(() {
    final listener = WidgetsBinding.instance.addPostFrameCallback((_) {
      size.value = MediaQuery.of(context).size;
    });
    return () => WidgetsBinding.instance.removeObserver(listener);
  }, []);
  return size.value;
}

Both examples demonstrate similar functionality for tracking window size, showcasing the syntax and structure differences between React and Flutter hooks.

42,148

🤖 Powerful asynchronous state management, server-state utilities and data fetching for the web. TS/JS, React Query, Solid Query, Svelte Query and Vue Query.

Pros of TanStack Query

  • Language-agnostic: Works with React, Vue, Svelte, and more
  • Robust caching and synchronization features
  • Extensive documentation and community support

Cons of TanStack Query

  • Steeper learning curve for complex use cases
  • Potentially overkill for simple state management needs
  • Not specifically designed for Flutter applications

Code Comparison

TanStack Query (React example):

const { data, isLoading, error } = useQuery('todos', fetchTodos)

if (isLoading) return 'Loading...'
if (error) return 'An error occurred: ' + error.message
return <div>{data.map(todo => <Todo key={todo.id} {...todo} />)}</div>

Flutter Hooks:

final todos = useMemoized(() => fetchTodos());
final snapshot = useFuture(todos);

if (snapshot.connectionState == ConnectionState.waiting) {
  return CircularProgressIndicator();
}
return ListView(children: snapshot.data.map((todo) => Todo(todo)).toList());

Key Differences

  • TanStack Query is more versatile across frameworks, while Flutter Hooks is Flutter-specific
  • TanStack Query offers more advanced data fetching and caching features
  • Flutter Hooks provides a simpler API for basic state management in Flutter apps
  • TanStack Query has a larger ecosystem and community support
  • Flutter Hooks integrates more seamlessly with Flutter's widget lifecycle

Both libraries aim to simplify state management and side effects, but cater to different ecosystems and use cases.

Convert Figma logo designs to code with AI

Visual Copilot

Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.

Try Visual Copilot

README

English | Português | 한국어 | 简体中文

Build codecov pub package pub package Discord

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 manage the life-cycle of a Widget. 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 {
  const Example({super.key, required this.duration});

  final Duration duration;

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

class _ExampleState extends State<Example> with SingleTickerProviderStateMixin {
  late final 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 logic 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 share 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({super.key, required this.duration});

  final Duration duration;

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

This code is functionally 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 has been 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 object with some specificities:

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

  • The same hook can be reused arbitrarily many 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 that they can easily be extracted into a package and published on pub for others to use.

Principle

Similar to State, hooks are stored in the Element of a Widget. However, instead of having one State, the Element stores a List<Hook>. Then in order 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 call returns the third hook and so on.

If this idea is still unclear, a naive implementation of hooks could look as follows:

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 hooks are implemented, here's a great article about how it was done 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-reloads while refactoring will break the application.

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

Consider the following list of hooks:

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

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

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

Here everything works fine and all hooks maintain their state.

Now consider that we removed HookB. We now have:

useA();
useC();

In this situation, HookA maintains its state but HookC gets hard reset. This happens because, when a hot-reload is performed after refactoring, all hooks after the first line impacted are disposed of. So, since HookC was placed after HookB, it will be disposed.

How to create a hook

There are two ways to create a hook:

  • A function

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

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

    ValueNotifier<T> useLoggedState<T>([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 class and have access to widget life-cycle and methods such as initHook, dispose and setState.

    It is usually good practice to hide the class under a function as such:

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

    The following code defines a hook that prints the total time a State has been alive on its dispose.

    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 already comes with a list of reusable hooks which are divided into different kinds:

Primitives

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

NameDescription
useEffectUseful for side-effects and optionally canceling them.
useStateCreates a variable and subscribes to it.
useMemoizedCaches the instance of a complex object.
useRefCreates an object that contains a single mutable property.
useCallbackCaches a function instance.
useContextObtains the BuildContext of the building HookWidget.
useValueChangedWatches a value and triggers a callback whenever its value changed.

Object-binding

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

dart:async related hooks:

NameDescription
useStreamSubscribes to a Stream and returns its current state as an AsyncSnapshot.
useStreamControllerCreates a StreamController which will automatically be disposed.
useOnStreamChangeSubscribes to a Stream, registers handlers, and returns the StreamSubscription.
useFutureSubscribes to a Future and returns its current state as an AsyncSnapshot.

Animation related hooks:

NameDescription
useSingleTickerProviderCreates a single usage TickerProvider.
useAnimationControllerCreates an AnimationController which will be automatically disposed.
useAnimationSubscribes to an Animation and returns its value.

Listenable related hooks:

NameDescription
useListenableSubscribes to a Listenable and marks the widget as needing build whenever the listener is called.
useListenableSelectorSimilar to useListenable, but allows filtering UI rebuilds
useValueNotifierCreates a ValueNotifier which will be automatically disposed.
useValueListenableSubscribes to a ValueListenable and return its value.
useOnListenableChangeAdds a given listener callback to a Listenable which will be automatically removed.

Misc hooks:

A series of hooks with no particular theme.

NameDescription
useReducerAn alternative to useState for more complex states.
usePreviousReturns the previous argument called to [usePrevious].
useTextEditingControllerCreates a TextEditingController.
useFocusNodeCreates a FocusNode.
useTabControllerCreates and disposes a TabController.
useScrollControllerCreates and disposes a ScrollController.
usePageControllerCreates and disposes a PageController.
useFixedExtentScrollControllerCreates and disposes a FixedExtentScrollController.
useAppLifecycleStateReturns the current AppLifecycleState and rebuilds the widget on change.
useOnAppLifecycleStateChangeListens to AppLifecycleState changes and triggers a callback on change.
useTransformationControllerCreates and disposes a TransformationController.
useIsMountedAn equivalent to State.mounted for hooks.
useAutomaticKeepAliveAn equivalent to the AutomaticKeepAlive widget for hooks.
useOnPlatformBrightnessChangeListens to platform Brightness changes and triggers a callback on change.
useSearchControllerCreates and disposes a SearchController.
useWidgetStatesControllerCreates and disposes a WidgetStatesController.
useExpansionTileControllerCreates a ExpansionTileController.
useDebouncedReturns a debounced version of the provided value, triggering widget updates accordingly after a specified timeout duration
useDraggableScrollableControllerCreates a DraggableScrollableController.

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 doesn'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 show 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 unless fully tested to avoid inadvertently breaking it in the future.

  • Add it to the README and write documentation for it.

Sponsors