qora_hooks

flutter_hooks integration for Qora — provides useQuery, useMutation, useInfiniteQuery, and useQueryClient.

Why a separate package?

flutter_hooks is an optional dependency. Bundling hooks inside qora or flutter_qora would impose it on every user, even those who prefer QoraBuilder / QoraMutationBuilder. A separate package keeps things opt-in.

Getting started

dependencies:
  flutter_qora: ^0.1.0   # QoraScope + QoraClient
  flutter_hooks: ^0.21.0 # HookWidget
  qora_hooks: ^0.1.0     # useQuery, useMutation, …

Wrap your app with QoraScope:

void main() {
  runApp(
    QoraScope(
      client: QoraClient(),
      child: const MyApp(),
    ),
  );
}

Usage

useQuery<T>

Fetches data on mount, caches it, and rebuilds the widget on every state change. Initialises from the cache synchronously — no loading flash when data is already fresh.

class UserScreen extends HookWidget {
  final String userId;
  const UserScreen({super.key, required this.userId});

  @override
  Widget build(BuildContext context) {
    final state = useQuery<User>(
      key: ['users', userId],
      fetcher: () => Api.getUser(userId),
      options: const QoraOptions(staleTime: Duration(minutes: 5)),
    );

    return switch (state) {
      Initial()             => const SizedBox.shrink(),
      Loading()             => const CircularProgressIndicator(),
      Success(:final data)  => UserCard(data),
      Failure(:final error) => ErrorView(error),
    };
  }
}

useMutation<TData, TVariables>

class EditProfileScreen extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final mutation = useMutation<User, UpdateUserInput>(
      mutator: (input) => Api.updateUser(input),
      options: MutationOptions(
        onSuccess: (user, _, __) async =>
            QoraScope.of(context).invalidate(['users', user.id]),
      ),
    );

    return ElevatedButton(
      onPressed: mutation.isPending
          ? null
          : () => mutation.mutate(UpdateUserInput(name: 'Alice')),
      child: mutation.isPending
          ? const CircularProgressIndicator()
          : const Text('Save'),
    );
  }
}

useInfiniteQuery<TData, TPageParam>

final query = useInfiniteQuery<PostsPage, String?>(
  key: const ['posts'],
  fetcher: (cursor) => Api.getPosts(cursor: cursor),
  getNextPageParam: (page) => page.nextCursor,
  initialPageParam: null,
);

final allPosts = query.pages.expand((p) => p.posts).toList();

Call query.fetchNextPage() when the user reaches the end of the list to load the next page. query.hasNextPage becomes false when getNextPageParam returns null.

useQueryClient

final client = useQueryClient(); // nearest QoraClient from QoraScope

Additional information

Libraries

qora_hooks
flutter_hooks integration for Qora.