
아래 글은 tanstack query 공식 문서(5.80.7)를 기반으로 쓰여졌습니다. 대부분의 예제와 설명은 tanstack query 문서에서 가져왔습니다.
import {
QueryClient,
QueryClientProvider,
} from '@tanstack/react-query'
const queryClient = new QueryClient()
export default function App() {
return (
<QueryClientProvider client={queryClient}>
// components....
</QueryClientProvider>
)
}
Query Client 의 인스턴스를 생성하고, QueryClientProvider 를 통해 전체 애플리케이션을 감싸면, Tanstack Query 에서 제공하는 모든 기능을 사용할 수 있게 된다.
Tanstack Query 에서 쿼리는 쿼리 키 로 연결된 비동기 데이터에 대한 종속성이다.
React 에서는 useQuery 에 쿼리 키와, promise 를 반환하는 쿼리 함수를 넣어 쿼리를 구독할 수 있다.
import { useQuery } from '@tanstack/react-query'
function App() {
const result = useQuery({
queryKey: ['todos'],
queryFn: async () => {
const response = await fetch(
'https://example-website-url.com',
)
return await response.json()
}
})
}
이때 사용된 쿼리 키 는 refetching, caching, query 공유에 내부적으로 사용된다.
쿼리 키와 일치하는 캐시된 데이터가 없다면, 쿼리 함수를 통해 새로운 데이터를 가져온다. 일치하는 캐시된 데이터가 있다면 그 데이터를 사용하게 된다.
이때 staleTime 옵션을 통해 캐시된 데이터가 언제동안 유효한지 설정할 수 있다.
gcTime옵션을 통해 유효하지 않은 캐시 데이터가 메모리에 남아있는 시간을 설정할 수 있다.
useQuery 를 통해 반환된 값에는 query 에 대한 정보들이 담겨져 있다.
import { useQuery } from '@tanstack/react-query'
function Todo() {
const { isPending, isError, data, error } = useQuery({
queryKey: ['todos'],
queryFn: async () => (await fetch(
'https://example-website-url.com',
).json()),
staleTime: 1000 * 60, // 60초 이후에 캐시된 데이터는 사용하지 않음
})
if (isPending) {
return <span>로딩중....</span>
}
if (isError) {
return <span>에러 발생!</span>
}
return (
<ul>
{data.map((todo) => (
<li key={todo.id}>{todo.title}</li>
<ul>
)
}
isPending, isError 이 아닌 status 로 상태를 표현할 수도 있다.
function Todo() {
const { status, data, error } = useQuery({
queryKey: ['todos'],
queryFn: fetchFn, // 다른 예제의 함수와 동일한 함수다.
})
if (status === 'pending') {
return <span>로딩중....</span>
}
if (status === 'error') {
return <span>에러 발생!</span>
}
return (
<ul>
{data.map((todo) => (
<li key={todo.id}>{todo.title}</li>
<ul>
)
}
Tanstack Query 는 쿼리 키를 기반으로 쿼리 캐싱을 관리한다. 쿼리 키는 배열로 사용되어야 하고, JSON.stringify 로 직렬화가 가능하고 고유한 값이라면 자유롭게 넣을 수 있다.
// 간단한 Query Key
useQuery({ queryKey: ['todos'], ... })
useQuery({ queryKey: ['something', 'special'], ... })
// 다양한 Query Key
useQuery({ queryKey: ['todo', 5], ... })
useQuery({ queryKey; ['todo', 5, { preview: true }], ... })
useQuery({ queryKey: ['todos', { type: 'done' }], ...})
다음과 같은 쿼리들은 모두 같은 쿼리로 여겨진다.
useQuery({ queryKey: ['todos', { status, page }], ... })
useQuery({ queryKey: ['todos', { page, status }], ... })
useQuery({ queryKey: ['todos', { page, status, other: undefined }], ...})
하지만 다음과 같은 쿼리들은 서로 다른 쿼리로 여겨진다.
useQuery({ queryKey: ['todos', status, page], ... })
useQuery({ queryKey: ['todos', page, status], ... })
useQuery({ queryKey: ['todos', undefined, page, status], ... })
만약 쿼리 함수가 변수의 값을 사용한다면, 쿼리 키에 포함해야 한다.
쿼리 함수가 변수에 따라 다르게 동작해야 한다면, 쿼리 키에 포함시켜 따로 캐싱되도록 해야한다, 변수의 값이 바뀌면, 쿼리는 자동으로 refetch 된다. (staleTime 설정에 따라 달라질 수 있음)
function Todos({ todoId }) {
const result = useQuery({
queryKey: ['todos', todoId],
queryFn: () => fetchTodoById(todoId),
})
}
Eslint 를 통해 Tanstack Query 의 올바른 사용과 실수를 방지할 수 있도록 플러그인을 제공하기도 한다. Eslint Plugin Query
쿼리 함수는 Promise 를 반환하는 함수라면 아무 함수나 사용할 수 있다.
useQuery({ queryKey: ['todos'], queryFn: fetchAllTodos })
useQuery({
queryKey: ['todos', todoId],
queryFn: () => fetchTodoById(todoId) }
)
useQuery({
queryKey: ['todos', todoId],
queryFn: async () => {
const data = await fetchTodoById(todoId)
return data
},
})
useQuery({
queryKey: ['todos', todoId],
queryFn: ({ queryKey }) => fetchTodoById(queryKey[1]),
})
Tanstack Query 가 해당 쿼리의 에러 여부를 판단하기 위해, 쿼리 함수는 반드시 reject 된 Promise 를 반환하거나 에러를 던질 수 있어야 한다.
const { error } = useQuery({
queryKey: ['todos', todoId],
queryFn: async () => {
if (somethingGoesWrong) {
throw new Error('Oh no!')
}
if (somethingElseGoesWrong) {
return Promise.reject(new Error('Oh no!'))
}
return data
},
})
axios 나 graphql-request 와 같은 라이브러리는 HTTP 통신 상태에 따라 자동으로 에러를 던지지만, fetch 와 같이 기본적으로 에러를 던지지 않는 함수는 직접 구현해야 한다.
useQuery({
queryKey: ['todos', todoId],
queryFn: async () => {
const response = await fetch('/todos/' + todoId)
if (!response.ok) {
throw new Error('Network response was not ok')
}
return response.json()
},
})
쿼리 함수에서 쿼리 키의 정보를 직접 다음과 같이 받아올 수 있다. 이는 QueryFunctionContext 에 의해 간편하게 처리된다.
function Todos({ status, page }) {
const result = useQuery({
queryKey: ['todos', { status, page }],
queryFn: fetchTodoList,
})
}
// Access the key, status and page variables in your query function!
function fetchTodoList({ queryKey }) {
const [_key, { status, page }] = queryKey
return new Promise()
}
QueryFunctionContext 는 각 쿼리 함수로 전달되는 객체로, 다음과 같은 값을 가지고 있다.
queryKey: QueryKeyclient: QueryClientsignal?: AbortSignalmeta: Record<string, unknown> | undefinedqueryOptions 헬퍼 함수를 통해 쿼리 설정을 간편하게 관리할 수 있다.
import { queryOptions } from '@tanstack/react-query'
function groupOptions(id: number) {
return queryOptions({
queryKey: ['groups', id],
queryFn: () => fetchGroups(id),
staleTime: 5 * 1000,
})
}
// usage:
useQuery(groupOptions(1))
useSuspenseQuery(groupOptions(5))
useQueries({
queries: [groupOptions(1), groupOptions(2)],
})
queryClient.prefetchQuery(groupOptions(23))
queryClient.setQueryData(
groupOptions(42).queryKey, newGroups
)
React Example: Simple Tanstack Query
공식 문서에서 아래 파트의 내용을 포함하고 있는 글입니다.