Prefetching with Hooks
The Hooks adapter provides convenient hooks for prefetching queries in your React-like Flutter components.
usePrefetchQuery
Section titled “usePrefetchQuery”The usePrefetchQuery hook returns a stable callback function for prefetching queries:
class UserCard extends HookWidget { final String userId;
const UserCard({required this.userId});
@override Widget build(BuildContext context) { final prefetch = usePrefetchQuery<User>();
return Card( child: InkWell( onTap: () => Navigator.pushNamed(context, '/user/$userId'), onHover: () => prefetch('user-$userId'.toQueryKey(), () => api.fetchUser(userId)), child: Column( children: [ Text('User $userId'), Text('Hover to prefetch profile data'), ], ), ), ); }}Features
Section titled “Features”- Stable Reference: The returned callback has a stable reference across renders
- Type Safety: Full type safety with generic parameters
- Error Handling: Prefetch errors are handled silently
- Cache Respect: Automatically respects cache staleness
usePrefetchOnMount
Section titled “usePrefetchOnMount”The usePrefetchOnMount hook prefetches queries when the component mounts:
class Dashboard extends HookWidget { @override Widget build(BuildContext context) { // Prefetch dashboard data on mount usePrefetchOnMount([ PrefetchConfig(queryKey: 'user-stats'.toQueryKey(), queryFn: () => api.fetchUserStats()), PrefetchConfig(queryKey: 'recent-posts'.toQueryKey(), queryFn: () => api.fetchRecentPosts()), PrefetchConfig(queryKey: 'notifications'.toQueryKey(), queryFn: () => api.fetchNotifications()), ]);
return DashboardContent(); }}Features
Section titled “Features”- Mount Trigger: Executes only when the component mounts
- Parallel Execution: All queries are prefetched in parallel
- Cleanup: Automatically handles cleanup when component unmounts
- Dependency Tracking: Re-executes if the configs list changes
Advanced Patterns
Section titled “Advanced Patterns”Conditional Prefetching
Section titled “Conditional Prefetching”Prefetch based on conditions:
class UserProfile extends HookWidget { final String userId; final bool shouldPrefetchRelated;
@override Widget build(BuildContext context) { final prefetch = usePrefetchQuery();
useEffect(() { if (shouldPrefetchRelated) { prefetch('user-posts-$userId'.toQueryKey(), () => api.fetchUserPosts(userId)); prefetch('user-followers-$userId'.toQueryKey(), () => api.fetchUserFollowers(userId)); } return null; }, [userId, shouldPrefetchRelated]);
return ProfileContent(); }}Navigation-Based Prefetching
Section titled “Navigation-Based Prefetching”Prefetch before navigation:
class UserList extends HookWidget { @override Widget build(BuildContext context) { final prefetch = usePrefetchQuery<User>();
return ListView.builder( itemBuilder: (context, index) { final userId = users[index].id;
return ListTile( title: Text(users[index].name), onTap: () { // Prefetch user details before navigation prefetch('user-$userId'.toQueryKey(), () => api.fetchUser(userId)); Navigator.pushNamed(context, '/user/$userId'); }, ); }, ); }}Tab-Based Prefetching
Section titled “Tab-Based Prefetching”Prefetch data for inactive tabs:
class TabbedInterface extends HookWidget { @override Widget build(BuildContext context) { final prefetch = usePrefetchQuery();
return DefaultTabController( length: 3, child: Column( children: [ TabBar( onTap: (index) { // Prefetch data for other tabs switch (index) { case 0: prefetch('posts'.toQueryKey(), () => api.fetchPosts()); prefetch('comments'.toQueryKey(), () => api.fetchComments()); break; case 1: prefetch('users'.toQueryKey(), () => api.fetchUsers()); prefetch('comments'.toQueryKey(), () => api.fetchComments()); break; case 2: prefetch('users'.toQueryKey(), () => api.fetchUsers()); prefetch('posts'.toQueryKey(), () => api.fetchPosts()); break; } }, tabs: [ Tab(text: 'Users'), Tab(text: 'Posts'), Tab(text: 'Comments'), ], ), Expanded( child: TabBarView( children: [ UsersTab(), PostsTab(), CommentsTab(), ], ), ), ], ), ); }}Integration with Routing
Section titled “Integration with Routing”Go Router Integration
Section titled “Go Router Integration”class AppRouter { static final router = GoRouter( routes: [ GoRoute( path: '/users', builder: (context, state) { // Prefetch user details on route mount return HookBuilder( builder: (context) { usePrefetchOnMount([ PrefetchConfig(queryKey: 'users'.toQueryKey(), queryFn: () => api.fetchUsers()), ]); return UsersPage(); }, ); }, routes: [ GoRoute( path: '/:userId', builder: (context, state) { final userId = state.pathParameters['userId']!; return HookBuilder( builder: (context) { usePrefetchOnMount([ PrefetchConfig(queryKey: 'user-$userId'.toQueryKey(), queryFn: () => api.fetchUser(userId)), PrefetchConfig(queryKey: 'user-posts-$userId'.toQueryKey(), queryFn: () => api.fetchUserPosts(userId)), ]); return UserProfilePage(userId: userId); }, ); }, ), ], ), ], );}Performance Tips
Section titled “Performance Tips”1. Use Stable References
Section titled “1. Use Stable References”The usePrefetchQuery hook returns a stable reference, so you can safely use it in event handlers:
// Good: Stable referencefinal prefetch = usePrefetchQuery();onHover: () => prefetch('key'.toQueryKey(), fetchFn);
// Avoid: Creating new functions in renderonHover: () => usePrefetchQuery()('key', fetchFn); // Creates new function each render2. Batch Related Prefetches
Section titled “2. Batch Related Prefetches”Use usePrefetchOnMount for multiple related queries:
// Good: Batch prefetchusePrefetchOnMount([ PrefetchConfig(queryKey: 'user'.toQueryKey(), queryFn: () => api.fetchUser()), PrefetchConfig(queryKey: 'posts'.toQueryKey(), queryFn: () => api.fetchPosts()),]);
// Less efficient: Individual prefetchesuseEffect(() { prefetch('user'.toQueryKey(), () => api.fetchUser()); prefetch('posts'.toQueryKey(), () => api.fetchPosts());}, []);3. Conditional Prefetching
Section titled “3. Conditional Prefetching”Only prefetch when conditions are met:
useEffect(() { if (isAuthenticated && hasPermission) { prefetch('sensitive-data'.toQueryKey(), () => api.fetchSensitiveData()); }}, [isAuthenticated, hasPermission]);Testing
Section titled “Testing”Test prefetching hooks:
testWidgets('usePrefetchQuery returns stable callback', (tester) async { late void Function(String, Future<String> Function()) prefetch1; late void Function(String, Future<String> Function()) prefetch2;
await tester.pumpWidget( MaterialApp( home: HookBuilder( builder: (context) { prefetch1 = usePrefetchQuery<String>(); prefetch2 = usePrefetchQuery<String>(); return SizedBox(); }, ), ), );
expect(prefetch1, equals(prefetch2));});
testWidgets('usePrefetchOnMount prefetches on mount', (tester) async { int fetchCount = 0;
Future<String> fetchData() async { fetchCount++; return 'test-data'; }
await tester.pumpWidget( MaterialApp( home: HookBuilder( builder: (context) { usePrefetchOnMount([ PrefetchConfig(queryKey: 'test-key'.toQueryKey(), queryFn: fetchData), ]); return SizedBox(); }, ), ), );
await tester.pump(); expect(fetchCount, equals(1));});The Hooks adapter makes prefetching intuitive and React-like, providing a clean API for predictive data loading in your Flutter applications.