
리액트 쿼리는 프론트엔드 개발을 하면서 들어본 적은 있었지만, 제대로 다뤄본 적은 없었습니다. 그러다 최근 수업 시간에 처음으로 접하게 되었고, 이번 기회를 통해 리액트 쿼리에 대해 더 깊이 알아보고자 했습니다.
그러면 알아봅시다요를레히 🔥
fetching, caching, 서버 데이터와의 동기화를 지원해주는 라이브러리
-> 웹 어플리케이션에서 서버 state를 손쉽게 페칭하고 캐싱해주며, 이를 동기적으로 업데이트할 수 있게 해줍니다.
기존 방식은 매번 fetch할 때마다 로딩, 에러, 데이터 상태를 직접 관리해야 해서 코드가 복잡해지고, store에 서버 데이터와 클라이언트 데이터를 함께 담으면 상태가 꼬여 예측이 어려워지는 문제가 있었습니다. 또한 데이터 수정 후에는 직접 갱신 처리를 해줘야 하고, 캐싱이 없어 동일한 요청을 반복하게 되는 비효율도 존재합니다.
이러한 문제를 해결하기 위해 React Query를 사용하는 이유를 3가지 알아보자!!
TanStack Query를 활용해서 데이터를 가져올 때는 항상 쿼리 키(queryKey)를 지정하게 됩니다. 이 쿼리 키는 캐시된 데이터와 비교해 새로운 데이터를 가져올지, 캐시된 데이터를 사용할지 결정하는 기준이 됩니다.
// 예시 코드
import { useQuery } from '@tanstack/react-query';
const { data } = useQuery(['user'], () =>
fetch('https://jsonplaceholder.typicode.com/users/1').then(res => res.json())
);
서버에서 데이터를 불러오고, 캐싱하고, 로딩/에러 상태를 관리해주는 핵심 훅
즉, 서버로부터 데이터를 요청하여 받아오는 GET api
보통 API 요청을 직접 useEffect + useState 조합으로 처리하던 방식을 대체하고 코드를 더 간결하고 안정적으로 만들어준다.
// 사용법
const {data} = useQuery(쿼리 키, 쿼리 함수, 옵션);
// 예시
const { data, isLoading, isPending, isFetching, refetch } = useQuery({
queryKey: ["about"],
queryFn: () =>
fetch("https://jsonplaceholder.typicode.com/posts")
.then((res) => res.json())
.then((res: { id: number; title: string; body: string }[]) =>
_.shuffle(res)
),
});
React Query에서 제공하는 hooks인 useQuery와 useMutation을 사용하면 해당 hooks의 queryKey를 지정
useQuery를 통해서 쿼리를 정의할 때 전달하는 함수입니다.
비동기 익명 함수, 정의해둔 함수를 전달할 수 있습니다.
쿼리에 사용되는 옵션 종류

반환 객체 종류

PUT, UPDATE, DELETE 와 같이 값을 변경할 때 사용하는 API다.
반환값은 useQuery와 동일하다!
// 첫 번째 방법
const deleteData = useMutation(() => axios.delete(`api/delete/${id}`));
// 두 번째 방법
const deleteData = useMutation({
mutationFn: (id) => axios.delete(`api/delete/${id}`)
})
이제 개념을 알았으니 직접 해보자!
npm add @tanstack/react-query
npm add @tanstack/react-query-devtools
react Query를 Next.js 앱에서 전역으로 사용할 수 있도록 세팅
// App > providers.tsx -> React Query 전역 설정
"use client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import React, { PropsWithChildren, useState } from "react";
type Props = {} & PropsWithChildren;
const Providers = ({ children }: Props) => {
const [queryclient] = useState(() => new QueryClient());
return (
<QueryClientProvider client={queryclient}>{children}</QueryClientProvider>
);
};
export default Providers;
// app > layout.tsx -> 앱 전체를 Provider로 감쌈
<Providers>{children}</Providers>
// app > middleware.tsx -> 요청을 그대로 통과시킴 (기본 설정)
export function middleware(request: NextRequest) {
return NextResponse.next();
}
React Query로 불러온 데이터를 가져와서 보여주는 페이지
// app > about > page.tsx
"use client";
import { useQuery } from "@tanstack/react-query";
import React from "react";
const AboutPage = () => {
const { data, isLoading } = useQuery({
queryKey: ["about"],
queryFn: () =>
fetch("https://jsonplaceholder.typicode.com/posts").then((res) =>
res.json()
),
});
if (isLoading) return <div>Loading...</div>;
return (
<ul className="p-4 flex flex-col gap-2">
{data?.map((item: { id: number; title: string; body: string }) => (
<li key={item.id} className="border rounded p-4 flex flex-col gap-2">
<h1 className="text-2xl font-bold">{item.title}</h1>
<p className="text-sm">{item.body}</p>
</li>
))}
</ul>
);
};
export default AboutPage;

처음에는 다소 생소하게 느껴졌지만, 직접 사용해보며 React Query가 널리 활용되는 이유를 체감할 수 있었습니다. 비동기 데이터 처리 과정이 훨씬 간결하고 체계적으로 개선되었으며, 코드의 가독성과 유지보수 측면에서도 큰 강점을 느꼈습니다.이번 경험을 통해 서버 상태 관리를 보다 효율적으로 접근할 수 있는 새로운 관점을 얻게 되었고, 앞으로 관련 작업에 있어 React Query를 적극 활용하고자 합니다.