# parallel-use-queries: Use useQueries for Dynamic Parallel Queries ## Priority: MEDIUM ## Explanation When you need to fetch multiple queries in parallel where the number or identity of queries is dynamic (e.g., fetching details for a list of IDs), use `useQueries`. It handles parallel execution and returns an array of query results. ## Bad Example ```tsx // Sequential fetching with useEffect - waterfall function UserProfiles({ userIds }: { userIds: string[] }) { const [users, setUsers] = useState([]) const [loading, setLoading] = useState(true) useEffect(() => { async function fetchAll() { const results = [] for (const id of userIds) { const user = await fetchUser(id) // Sequential! results.push(user) } setUsers(results) setLoading(false) } fetchAll() }, [userIds]) // N requests run one after another } // Multiple useQuery calls - breaks rules of hooks function UserProfiles({ userIds }: { userIds: string[] }) { // Can't call hooks in a loop! const queries = userIds.map(id => useQuery({ queryKey: ['user', id], queryFn: () => fetchUser(id), })) } ``` ## Good Example ```tsx import { useQueries } from '@tanstack/react-query' function UserProfiles({ userIds }: { userIds: string[] }) { const userQueries = useQueries({ queries: userIds.map(id => ({ queryKey: ['users', id], queryFn: () => fetchUser(id), staleTime: 5 * 60 * 1000, })), }) const isLoading = userQueries.some(q => q.isLoading) const isError = userQueries.some(q => q.isError) const users = userQueries.map(q => q.data).filter(Boolean) if (isLoading) return if (isError) return return (
    {users.map(user => (
  • {user.name}
  • ))}
) } ``` ## Good Example: With Combine Option ```tsx function UserProfiles({ userIds }: { userIds: string[] }) { const { data: users, isPending } = useQueries({ queries: userIds.map(id => ({ queryKey: ['users', id], queryFn: () => fetchUser(id), })), // Combine results into single value combine: (results) => ({ data: results.map(r => r.data).filter(Boolean), isPending: results.some(r => r.isPending), isError: results.some(r => r.isError), }), }) if (isPending) return return } ``` ## Good Example: Dependent Parallel Queries ```tsx function PostsWithAuthors({ postIds }: { postIds: string[] }) { // First: fetch all posts in parallel const postQueries = useQueries({ queries: postIds.map(id => ({ queryKey: ['posts', id], queryFn: () => fetchPost(id), })), }) const posts = postQueries.map(q => q.data).filter(Boolean) const authorIds = [...new Set(posts.map(p => p.authorId))] // Then: fetch all unique authors in parallel const authorQueries = useQueries({ queries: authorIds.map(id => ({ queryKey: ['users', id], queryFn: () => fetchUser(id), enabled: posts.length > 0, // Wait for posts })), }) // Combine data... } ``` ## Good Example: With Suspense ```tsx import { useSuspenseQueries } from '@tanstack/react-query' function UserProfiles({ userIds }: { userIds: string[] }) { const userQueries = useSuspenseQueries({ queries: userIds.map(id => ({ queryKey: ['users', id], queryFn: () => fetchUser(id), })), }) // All data guaranteed - no loading states needed const users = userQueries.map(q => q.data) return } ``` ## Context - Queries run in parallel, not sequentially - Each query is cached independently - Use `combine` to transform results array into single value - Empty queries array is valid (returns empty results) - Pairs well with `useSuspenseQueries` for guaranteed data - Individual query options (staleTime, etc.) apply per-query