initial commit
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
# 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<User[]>([])
|
||||
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 <Loading />
|
||||
if (isError) return <Error />
|
||||
|
||||
return (
|
||||
<ul>
|
||||
{users.map(user => (
|
||||
<li key={user.id}>{user.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## 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 <Loading />
|
||||
|
||||
return <UserList users={users} />
|
||||
}
|
||||
```
|
||||
|
||||
## 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 <UserList users={users} />
|
||||
}
|
||||
```
|
||||
|
||||
## 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
|
||||
Reference in New Issue
Block a user