Skip to content

Hooks Adapter Examples

Complete working examples using the Fasq Hooks adapter.

A complete example showing user listing, creation, and deletion:

import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:fasq_hooks/fasq_hooks.dart';
class User {
final String id;
final String name;
final String email;
User({required this.id, required this.name, required this.email});
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'],
name: json['name'],
email: json['email'],
);
}
}
class ApiService {
Future<List<User>> fetchUsers() async {
await Future.delayed(Duration(seconds: 1)); // Simulate network
return [
User(id: '1', name: 'Alice', email: 'alice@example.com'),
User(id: '2', name: 'Bob', email: 'bob@example.com'),
];
}
Future<User> createUser(Map<String, String> data) async {
await Future.delayed(Duration(seconds: 1));
return User(
id: DateTime.now().millisecondsSinceEpoch.toString(),
name: data['name']!,
email: data['email']!,
);
}
Future<void> deleteUser(String id) async {
await Future.delayed(Duration(seconds: 1));
}
}
class UserManagementScreen extends HookWidget {
@override
Widget build(BuildContext context) {
final usersState = useQuery<List<User>>('users', () => ApiService().fetchUsers());
final createUser = useMutation<User, Map<String, String>>(
(data) => ApiService().createUser(data),
options: MutationOptions(
onSuccess: (user) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('User ${user.name} created!')),
);
useQueryClient().invalidateQuery('users');
},
),
);
final deleteUser = useMutation<void, String>(
(id) => ApiService().deleteUser(id),
options: MutationOptions(
onSuccess: (_, id) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('User deleted')),
);
useQueryClient().invalidateQuery('users');
},
),
);
return Scaffold(
appBar: AppBar(title: Text('User Management')),
body: Column(
children: [
Expanded(
child: usersState.when(
idle: () => Center(child: Text('Ready to load users')),
loading: () => Center(child: CircularProgressIndicator()),
error: (error, stack) => Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Error: $error'),
ElevatedButton(
onPressed: () => useQueryClient().getQueryByKey<List<User>>('users')?.fetch(),
child: Text('Retry'),
),
],
),
),
data: (users) => ListView.builder(
itemCount: users.length,
itemBuilder: (context, index) {
final user = users[index];
return ListTile(
title: Text(user.name),
subtitle: Text(user.email),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: deleteUser.isLoading ? null : () => deleteUser.mutate(user.id),
),
);
},
),
),
),
Padding(
padding: const EdgeInsets.all(16.0),
child: ElevatedButton(
onPressed: createUser.isLoading ? null : () => _showCreateUserDialog(context, createUser),
child: createUser.isLoading
? CircularProgressIndicator()
: Text('Add User'),
),
),
],
),
);
}
void _showCreateUserDialog(BuildContext context, MutationState<User, Map<String, String>> createUser) {
showDialog(
context: context,
builder: (context) => CreateUserDialog(createUser: createUser),
);
}
}
class CreateUserDialog extends HookWidget {
final MutationState<User, Map<String, String>> createUser;
const CreateUserDialog({required this.createUser});
@override
Widget build(BuildContext context) {
final nameController = useTextEditingController();
final emailController = useTextEditingController();
return AlertDialog(
title: Text('Create User'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: nameController,
decoration: InputDecoration(labelText: 'Name'),
),
TextField(
controller: emailController,
decoration: InputDecoration(labelText: 'Email'),
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('Cancel'),
),
ElevatedButton(
onPressed: createUser.isLoading ? null : () {
createUser.mutate({
'name': nameController.text,
'email': emailController.text,
});
Navigator.pop(context);
},
child: createUser.isLoading
? CircularProgressIndicator()
: Text('Create'),
),
],
);
}
}

Implementing pagination with hooks:

class PaginatedUsersScreen extends HookWidget {
@override
Widget build(BuildContext context) {
final page = useState(1);
final pageSize = 10;
final usersState = useQuery<List<User>>(
'users:page:${page.value}',
() => ApiService().fetchUsersPage(page.value, pageSize),
options: QueryOptions(
staleTime: Duration(minutes: 5), // Keep pages fresh for 5 minutes
),
);
return Scaffold(
appBar: AppBar(title: Text('Paginated Users')),
body: Column(
children: [
Expanded(
child: usersState.when(
loading: () => Center(child: CircularProgressIndicator()),
error: (error, stack) => Center(child: Text('Error: $error')),
data: (users) => ListView.builder(
itemCount: users.length,
itemBuilder: (context, index) {
final user = users[index];
return ListTile(
title: Text(user.name),
subtitle: Text(user.email),
);
},
),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: page.value > 1 ? () => page.value-- : null,
child: Text('Previous'),
),
Text('Page ${page.value}'),
ElevatedButton(
onPressed: () => page.value++,
child: Text('Next'),
),
],
),
],
),
);
}
}

Simulating real-time updates with polling:

class RealTimeUsersScreen extends HookWidget {
@override
Widget build(BuildContext context) {
final usersState = useQuery<List<User>>(
'users:realtime',
() => ApiService().fetchUsers(),
options: QueryOptions(
refetchInterval: Duration(seconds: 30), // Poll every 30 seconds
refetchOnWindowFocus: true, // Refetch when app regains focus
),
);
return Scaffold(
appBar: AppBar(
title: Text('Real-time Users'),
actions: [
IconButton(
icon: Icon(Icons.refresh),
onPressed: () => useQueryClient().getQueryByKey<List<User>>('users:realtime')?.fetch(),
),
],
),
body: usersState.when(
loading: () => Center(child: CircularProgressIndicator()),
error: (error, stack) => Center(child: Text('Error: $error')),
data: (users) => Column(
children: [
if (usersState.isFetching && !usersState.isLoading)
LinearProgressIndicator(),
Expanded(
child: ListView.builder(
itemCount: users.length,
itemBuilder: (context, index) {
final user = users[index];
return ListTile(
title: Text(user.name),
subtitle: Text(user.email),
trailing: Text('Updated: ${DateTime.now().toString().substring(11, 19)}'),
);
},
),
),
],
),
),
);
}
}

Implementing optimistic updates for instant feedback:

class OptimisticUserScreen extends HookWidget {
@override
Widget build(BuildContext context) {
final usersState = useQuery<List<User>>('users', () => ApiService().fetchUsers());
final updateUser = useMutation<User, User>(
(user) => ApiService().updateUser(user),
options: MutationOptions(
onMutate: (updatedUser) {
// Optimistically update cache
final currentUsers = useQueryClient().getQueryData<List<User>>('users');
final optimisticUsers = currentUsers?.map((u) =>
u.id == updatedUser.id ? updatedUser : u
).toList();
useQueryClient().setQueryData('users', optimisticUsers);
},
onSuccess: (user) {
// Invalidate to get fresh data
useQueryClient().invalidateQuery('users');
},
onError: (error) {
// Rollback on error
useQueryClient().invalidateQuery('users');
},
),
);
return Scaffold(
appBar: AppBar(title: Text('Optimistic Updates')),
body: usersState.when(
loading: () => Center(child: CircularProgressIndicator()),
error: (error, stack) => Center(child: Text('Error: $error')),
data: (users) => ListView.builder(
itemCount: users.length,
itemBuilder: (context, index) {
final user = users[index];
return ListTile(
title: Text(user.name),
subtitle: Text(user.email),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: Icon(Icons.edit),
onPressed: updateUser.isLoading ? null : () {
final updatedUser = User(
id: user.id,
name: '${user.name} (Updated)',
email: user.email,
);
updateUser.mutate(updatedUser);
},
),
],
),
);
},
),
),
);
}
}

Creating a reusable custom hook:

// Custom hook for user management
QueryState<List<User>> useUsers() {
return useQuery<List<User>>(
'users',
() => ApiService().fetchUsers(),
options: QueryOptions(
staleTime: Duration(minutes: 5),
),
);
}
MutationState<User, Map<String, String>> useCreateUser() {
return useMutation<User, Map<String, String>>(
(data) => ApiService().createUser(data),
options: MutationOptions(
onSuccess: (user) {
useQueryClient().invalidateQuery('users');
},
),
);
}
MutationState<void, String> useDeleteUser() {
return useMutation<void, String>(
(id) => ApiService().deleteUser(id),
options: MutationOptions(
onSuccess: (_, id) {
useQueryClient().invalidateQuery('users');
},
),
);
}
// Using the custom hooks
class UserScreenWithCustomHooks extends HookWidget {
@override
Widget build(BuildContext context) {
final usersState = useUsers();
final createUser = useCreateUser();
final deleteUser = useDeleteUser();
return Scaffold(
appBar: AppBar(title: Text('Custom Hooks Example')),
body: usersState.when(
loading: () => Center(child: CircularProgressIndicator()),
error: (error, stack) => Center(child: Text('Error: $error')),
data: (users) => ListView.builder(
itemCount: users.length,
itemBuilder: (context, index) {
final user = users[index];
return ListTile(
title: Text(user.name),
subtitle: Text(user.email),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: deleteUser.isLoading ? null : () => deleteUser.mutate(user.id),
),
);
},
),
),
);
}
}

Implementing error boundaries with hooks:

class ErrorBoundary extends HookWidget {
final Widget child;
final Widget Function(Object error)? errorBuilder;
const ErrorBoundary({
required this.child,
this.errorBuilder,
});
@override
Widget build(BuildContext context) {
final error = useState<Object?>(null);
return Builder(
builder: (context) {
if (error.value != null) {
return errorBuilder?.call(error.value!) ??
Center(child: Text('Error: ${error.value}'));
}
return child;
},
);
}
}
class UsersScreenWithErrorBoundary extends HookWidget {
@override
Widget build(BuildContext context) {
return ErrorBoundary(
errorBuilder: (error) => Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.error, size: 64, color: Colors.red),
SizedBox(height: 16),
Text('Something went wrong'),
SizedBox(height: 8),
Text('$error'),
SizedBox(height: 16),
ElevatedButton(
onPressed: () => Navigator.pop(context),
child: Text('Go Back'),
),
],
),
),
child: UserManagementScreen(),
);
}
}

Optimizing performance with proper cache configuration:

class OptimizedUsersScreen extends HookWidget {
@override
Widget build(BuildContext context) {
final usersState = useQuery<List<User>>(
'users',
() => ApiService().fetchUsers(),
options: QueryOptions(
staleTime: Duration(minutes: 10), // Keep fresh for 10 minutes
cacheTime: Duration(minutes: 30), // Keep in cache for 30 minutes
refetchOnWindowFocus: false, // Don't refetch on focus
refetchOnMount: false, // Don't refetch on mount if data exists
),
);
return Scaffold(
appBar: AppBar(title: Text('Optimized Users')),
body: usersState.when(
loading: () => Center(child: CircularProgressIndicator()),
error: (error, stack) => Center(child: Text('Error: $error')),
data: (users) => ListView.builder(
itemCount: users.length,
itemBuilder: (context, index) {
final user = users[index];
return ListTile(
title: Text(user.name),
subtitle: Text(user.email),
);
},
),
),
);
}
}