TanStack Query( 이전 명칭: React Query )는 웹 애플리케이션에서 서버 상태를 가져오고, 캐싱하고, 동기화하고, 업데이트하는 작업을 쉽게 해줍니다.
설치
pnpm add @tanstack/react-query
프로젝트에 적용
Provider
tanstack-query를 사용하기 위해 최상단에 <QueryClientProvider>를 렌더링합니다.
app router의 경우 layout에 추가합니다. ( 한번 더 감싸는 Provider 컴포넌트를 사용하도록 구현 )
const queryClient = new QueryClient()
function App() {
return ( // Provide the client to your App
<QueryClientProvider client={queryClient}>
<Todos />
</QueryClientProvider>
)
}
Queries
unique key로 구분되는 비동기 데이터를 관리합니다.
서버로부터 받아온 GET이나 POST 요청의 응답 데이터를 가져올 수 있습니다.
function Todos() {
const { data } = useQuery({ queryKey: ['todos'], queryFn: getTodos })
return (
<div>
<ul>
{data?.map((todo) =>
<li key={todo.id}>{todo.title}</li>
)}
</ul>
</div>
)
}
💡 useQuery에서 API를 통해 조회한 결과를 다시 받아오고 싶다면?
useQuery의 refetch 메소드를 사용하여 요청 결과를 다시 받아올 수 있습니다.
❗️ 만약 다른 컴포넌트에서 이 작업을 하고자 한다면 useQueryClient를 사용합니다.
function Todos() {
// Access the client
const queryClient = useQueryClient()
// Queries
const { data } = useQuery({ queryKey: ['todos'], queryFn: getTodos })
return (
<div>
<ul>
{data?.map((todo) => <li key={todo.id}>{todo.title}</li>)}
</ul>
<button onClick={() => { queryClient.invalidateQueries({ queryKey: ['todos'] }) }}>Add Todo</button>
</div>
)
}
queryKey를 constants 폴더 하위에 유니크하게 상수로 관리하면 다른 컴포넌트에서 특정 쿼리의 캐시 제거를 손쉽게 할 수 있습니다.
💡 useQuery 옵션
- enabled : enabled 옵션으로 넘기는 값이 false인 경우 queryFn을 실행하지 않습니다.
- id로 단건을 조회하는 API가 있으면,
enabled : !!id 로 옵션을 설정하여 잘못된 요청을 하지 않도록 방지할 수 있습니다.
- id로 단건을 조회하는 API가 있으면,
- refetchOnWindowFocus : 윈도우에 포커스 됐을때 요청을 다시 보낼지 여부를 true/false로 전달합니다.
- default : true
- 브라우저에 포커스가 될때마다 다시 조회해야하는 데이터가 아닌 경우 이 옵션을 끌 수 있습니다.
- retry : 요청이 실패할 경우 다시 시도하는 횟수를 설정합니다.
- default : 3
💡 Infinity Scroll 적용을 위한 useInfiniteQuery
무한 스크롤을 적용하고 싶은 경우에 API로 page 값을 변경하여 요청하고 전체 응답값을 하나의 상태값으로 관리하는 기능이 필요합니다.
이런 경우에 useInfiniteQuery를 사용하여 편리하게 전체 응답값을 하나의 상태로 관리할 수 있습니다.
import { useInfiniteQuery } from '@tanstack/react-query'
function Projects() {
const fetchProjects = async ({ pageParam }) => {
const res = await fetch('/api/projects?cursor=' + pageParam)
return res.json()
}
const {
data,
error,
fetchNextPage,
hasNextPage,
isFetching,
isFetchingNextPage,
status,
} = useInfiniteQuery({
queryKey: ['projects'],
queryFn: fetchProjects,
initialPageParam: 0,
getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
})
return status === 'pending'
? ( <p>Loading...</p> )
: status === 'error'
? ( <p>Error: {error.message}</p> )
: (
<>
{data.pages.map((group, i) =>(
<React.Fragment key={i}>
{group.data.map((project) => (
<p key={project.id}>{project.name}</p>
))}
</React.Fragment>
))}
<div>
<button
onClick={() => fetchNextPage()}
disabled={!hasNextPage || isFetchingNextPage}
>
{isFetchingNextPage
? 'Loading more...'
: hasNextPage
? 'Load More'
: 'Nothing more to load'
}
</button>
</div>
<div>{isFetching && !isFetchingNextPage ? 'Fetching...' : null}</div>
</>
)
}
Mutations
데이터를 생성/업데이트/삭제하거나 서버 사이드 이펙트를 수행하는 데 사용됩니다.
function App() {
const { isPending, isError, mutate } = useMutation({
mutationFn: (newTodo) => { return axios.post('/todos', newTodo) },
})
return (
<div>
{isPending ? (
'Adding todo...'
) : (
<>
{isError ? (
<div>An error occurred: {mutation.error.message}</div>
) : null}
{isSuccess ? <div>Todo added!</div> : null}
<button
onClick={() => { mutate({ id: new Date(), title: 'Do Laundry' }) }}
>
Create Todo
</button>
</>
)}
</div>
)
}
💡 mutate 메소드 비동기 처리가 필요한 경우
function App() {
const { isPending, isError, mutateAsync } = useMutation({
mutationFn: (newTodo) => {
return axios.post('/todos', newTodo);
},
});
return (
<div>
{isPending ? (
'Adding todo...'
) : (
<>
{' '}
{isError ? <div>An error occurred: {mutation.error.message}</div> : null} {isSuccess ? <div>Todo added!</div> : null}
<button
onClick={async () => {
await mutateAsync({ id: new Date(), title: 'Do Laundry' });
}}
>
Create Todo
</button>
</>
)}
</div>
);
}
💡 데이터 생성/수정/삭제 응답 결과에 따라 특정 로직을 수행하고 싶을 때
데이터 생성/수정/삭제 응답 결과에 따라 특정 로직을 수행하고 싶을 때 다음 콜백 메소드를 활용할 수 있습니다.
- onError : 에러 발생 시 수행
- onSuccess : 요청 성공 시 수행
- onSettled : 요청 성공 여부와 관계없이 마지막 단계에 수행
useMutation({
mutationFn: addTodo,
onMutate: (variables, context) => {
// A mutation is about to happen!
// Optionally return a result containing data to use when for example rolling back
return { id: 1 }
},
onError: (error, variables, onMutateResult, context) => {
// An error happened!
console.log(`rolling back optimistic update with id ${onMutateResult.id}`)
},
onSuccess: (data, variables, onMutateResult, context) => {
// Boom baby!
},
onSettled: (data, error, variables, onMutateResult, context) => {
// Error or success... doesn't matter!
},
})
💡 mutate가 onSuccess일때 특정 query data를 업데이트하고 싶을 때
setQueryData 메소드를 활용하여 특정 query의 data를 업데이트합니다.
const useMutateTodo = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: editTodo,
onSuccess: (data, variables) => {
queryClient.setQueryData(['todo', { id: variables.id }], data);
},
});
};
'Next.js' 카테고리의 다른 글
| [Next.js] 차트 라이브러리 비교 (0) | 2025.10.01 |
|---|---|
| [Next.js] Code Splitting (0) | 2025.10.01 |
| [Next.js] 빌드 타임 개선 (0) | 2025.10.01 |