무엇
useQueries는 여러 쿼리를 한 번에 실행하는 훅이다. 쿼리 개수가 렌더마다 달라질 수 있을 때 쓴다 — 훅은 반복문·조건문 안에서 부를 수 없기 때문이다.
queries 배열에 useQuery와 같은 옵션 객체를 넣으면, 각 항목에 대응하는 결과 배열을 입력 순서 그대로 돌려준다.
const results = useQueries({
queries: ids.map((id) => ({
queryKey: ['todo', id],
queryFn: () => fetchTodo(id),
})),
})
무슨 일이 일어나나
개수가 고정된 병렬 쿼리라면 useQuery를 여러 번 나란히 부르면 된다. 하지만 개수가 데이터에 따라 변하면(예: id 목록) 그럴 수 없다 — 훅 호출 순서가 렌더마다 바뀌면 안 되기 때문이다. useQueries는 이 제약을 우회해, 배열 하나로 N개의 쿼리를 관리한다.
각 결과 항목은 useQuery의 반환과 동일하다(data·status·isPending 등). 캐시도 각자의 queryKey로 독립적으로 관리되므로, 다른 컴포넌트의 같은 키 쿼리와 자연스럽게 공유된다.
combine 옵션으로 결과 배열을 하나의 값으로 합칠 수 있다. 합쳐진 결과는 구조적 공유로 참조 안정성을 유지한다. 다만 인라인으로 넘긴 combine은 매 렌더 실행되므로, 무거우면 useCallback으로 감싼다.
사용법
id 목록으로 여러 쿼리를 만들고, 배열 결과를 그대로 다룬다. 전체가 준비됐는지는 결과 배열을 훑어 판단한다.
import { useQueries } from '@tanstack/react-query'
export function Todos({ ids }: { ids: string[] }) {
const results = useQueries({
queries: ids.map((id) => ({
queryKey: ['todo', id],
queryFn: () => fetchTodo(id),
staleTime: 60_000,
})),
})
const isPending = results.some((r) => r.isPending)
if (isPending) return <p>로딩 중</p>
return <ul>{results.map((r) => <li key={r.data!.id}>{r.data!.title}</li>)}</ul>
}
실무 예시
combine으로 여러 결과를 하나의 요약으로 합치는 경우다. 개별 결과 대신 합쳐진 값 하나만 컴포넌트에서 쓴다.
export function useTodoSummary(ids: string[]) {
return useQueries({
queries: ids.map((id) => ({
queryKey: ['todo', id],
queryFn: () => fetchTodo(id),
})),
combine: (results) => ({
data: results.map((r) => r.data),
isPending: results.some((r) => r.isPending),
doneCount: results.filter((r) => r.data?.done).length,
}),
})
}
왜 중요한가
대부분의 병렬 데이터는 useQuery를 여러 번 부르면 해결되므로 useQueries가 필요한 경우는 좁다 — 쿼리 개수가 런타임에 정해질 때다. 그 상황에선 훅 규칙 때문에 대안이 마땅치 않아, 사실상 유일한 정답에 가깝다. 자주 쓰이진 않지만, 필요할 때 이걸 모르면 우회로가 지저분해진다.
Reference