fuery_hooks 1.5.1
fuery_hooks: ^1.5.1 copied to clipboard
Hooks for Fuery: useQuery, useInfiniteQuery, useMutation, useQueries, and useMutationState for flutter_hooks, with useOnQueryChange and useOnMutationChange for side effects.
Hooks for Fuery: render cached server data from inside build, in a flutter_hooks HookWidget.
Fuery is built the Flutter way. Queries are defined outside build, and widgets render them: builders for UI and listeners for side effects, in the shape of StreamBuilder. And fuery depends on nothing beyond Dart and Flutter.
fuery_hooks is for developers who prefer hooks, a style many know from web development. It is a package of its own because it depends on flutter_hooks, so only apps that choose hooks get that dependency. It renders the same queries with one call per query, and no builders:
final todosQuery = Query(
queryKey: ['todos'],
queryFn: (_) => api.getTodos(),
);
class TodoListScreen extends HookWidget {
const TodoListScreen({super.key});
@override
Widget build(BuildContext context) {
final todos = useQuery(todosQuery);
return switch (todos) {
QueryResult(:final data?) => TodoList(data),
QueryResult(:final error?) => Text('$error'),
_ => const CircularProgressIndicator(),
};
}
}
The queries, mutations, and client are Fuery's own, so a screen written with hooks and a screen written with widgets share one cache and one request.
Read the hooks guide → · Fuery documentation →
Install #
flutter pub add fuery_hooks flutter_hooks
fuery_hooks re-exports fuery, so package:fuery_hooks/fuery_hooks.dart and package:flutter_hooks/flutter_hooks.dart are the only imports you need.
Hooks #
| Hook | Returns |
|---|---|
useQuery(query) |
The latest QueryResult. refetch() is on the result. |
useInfiniteQuery(query) |
The latest InfiniteQueryResult. fetchNextPage() is on the result. |
useMutation(mutation) |
The latest MutationResult. mutate(...) is on the result. |
useQueries(queries) |
The results of a list of queries of one data type, in order. |
useMutationState(mutation) |
The state of every run of a mutation, oldest first, wherever it started, found by its mutationKey. |
useQueryClient() |
The client the hooks use, for invalidateQueries and setData. |
Each rebuilds the widget when its result changes, and needs no type arguments: todos above is a QueryResult<List<Todo>> because api.getTodos() returns a Future<List<Todo>>. They only read. Change hooks run side effects.
Changing data #
final addTodo = useMutation(addTodoMutation);
ElevatedButton(
onPressed: addTodo.isPending ? null : () => addTodo.mutate('Buy milk'),
child: const Text('Add'),
)
A NoVariablesMutation runs with mutate(null), as from a MutationBuilder:
final logout = useMutation(logoutMutation);
TextButton(
onPressed: () => logout.mutate(null),
child: const Text('Log out'),
)
For a side effect of one call, pass MutateOptions to mutate. The request itself still finishes when the widget goes away. For a definition, the callbacks of its calls are dropped then. A shared observer is left alone and still runs them, so check context.mounted in them.
Reacting to changes #
For navigation, snackbars, and other one-off effects, pass what a hook returns to a change hook. Its listener runs after the change, never during a build, and not for the result the widget mounts with. listenWhen compares the previous result with the new one, as on QueryListener:
final addTodo = useMutation(addTodoMutation);
useOnMutationChange(
addTodo,
listenWhen: (previous, current) => current.isSuccess,
listener: (context, result) => Navigator.pop(context),
);
| Hook | Calls its listener after |
|---|---|
useOnQueryChange(result, listener: ...) |
Each change of the result of useQuery or useInfiniteQuery. |
useOnMutationChange(result, listener: ...) |
Each change of the result of useMutation: the runs started with it, or every run of a shared observer. |
useOnMutationStateChange(mutation, listener: ...) |
Each change of each run of a mutation, found by its mutationKey, from any widget. |
See Reacting to changes for which effect goes where.
Rules #
- Pass a definition. A
Queryor aMutationcan be a top-level value or be built inbuild: the hook keeps one observer for it and updates its options, so a new key shows in the same frame. - Don't call
.observe()inbuild. A new query observer every build subscribes and fetches again, and a new mutation observer starts idle. In debug builds the hook prints a warning once per key. - Run side effects in a change hook. For a snackbar or navigation, use
useOnQueryChange,useOnMutationChange, oruseOnMutationStateChange.useEffectanduseValueChangedrun during the build, where those calls fail. - Watch the client with a memoized stream:
useStream(useMemoized(() => client.watch(selector), [client])). A new stream every build rebuilds the widget on every frame. - The client is the one a
FueryProviderabove provides, orFuery.client, anduseQueryClient()returns it. A shared observer keeps the client it was created with instead.
Everything else, from keys and freshness to persistence and devtools, is Fuery's. See the documentation.