
데이터의 Fetching, Caching, 동기화, 서버 데이터 업데이트 등을 위한 라이브러리이다.
서버 상태(로딩, 에러, 성공 등)를 불러오고, 지속적으로 동기화하고 업데이트 하는 작업을 도와주는 라이브러리이다.
React-Query는 캐싱을 통해 동일한 데이터에 대한 반복적인 비동기 데이터 호출을 방지하고, 불필요한 API콜을 줄여 서버 과부하를 방지한다.
API요청을 하면 자동으로 데이터를 캐싱하고, 같은 요청이 들어오면 데이터를 재사용한다.
설정한 시간이 지나거나, 특정 조건이 충족되면 자동으로 데이터를 새로고침 (refetch) 한다.
데이터 갱신 시점에 대한 옵션
refetchOnWindowFocus, //default: true 브라우저에 포커스
refetchOnMount, //default: true 새 컴포넌트가 마운트
refetchOnReconnect, //default: true 네트워크 재연결
staleTime, //default: 0 fresh->stale 시간
cacheTime, //default: 5분 (60 5 1000) 캐싱된 상태로 남는 시간
기존의 data fetching 과정을 단순화 하여 보일러 플레이트를 줄어준다.
기존의 과정인
1. data를 위한 상태 선언
2. fetching 로직 구성
3. useEffect에서 메서드 사용 후 상태에 저장
과정을 react-query 선언 한줄로 줄일 수 있다.
const getBoardList = async => {
...
return data;
}
function App() {
const [data, setData] = useState();
useEffect(()=>{
getBoardList().then(list => {
setData(list);
}
},[]);
return <div>{data[0]}</div>
}
export default App;
const getBoardList = async => {
...
return data;
}
function App() {
const {data} = useQuery(["board"],getBoardList)
return <div>{data[0]}</div>
}
export default App;
redux, recoil등 상태관련 라이브러리들이 이미 존재하지만, 이들은 클라이언트 데이터를 다루는 데에 중점적이고, 이런 라이브러리들이 Server 데이터 까지 관리하게되면 효율적인 관리에 문제가 발생 할 수 있다.
React-Query는 컴포넌트 내부에서 onSuccess, onError등의 함수를 통해 fetch 서버 상태에 따른 핸들링을 처리 할 수 있다.
import { useQuery } from "@tanstack/react-query";
import axios from "axios";
const fetchPosts = async () => {
const { data } = await axios.get("https://jsonplaceholder.typicode.com/posts");
return data;
};
function Posts() {
const { data, isLoading, error } = useQueries({
["posts"], // 캐싱 키 (중복 방지)
fetchPosts,
{
onSuccess: (data) => {
//
}
},
{
onError: (error) => {
//
}
}
});
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<ul>
{data.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
export default Posts;
isLoading → 데이터 로딩 중 여부
error → API 요청 중 에러 발생 여부
option을 통해 주어진 주기나, 조건 마다 데이터를 자동으로 fetch 하여 업데이트 할 수 있다.
const { data, refetch } = useQuery({
queryKey: ["posts"],
queryFn: fetchPosts,
refetchInterval: 5000, // 5초마다 데이터 자동 갱신
});
const { data } = useQuery({
queryKey: ["posts"],
queryFn: fetchPosts,
refetchIntervalInBackground: true, // 백그라운드에서도 새로고침 유지
});
앞서 언급한 refetchOnWindowFocus,refetchOnReconnect 등
refetch를 통해 데이터를 수동으로 업데이트 할 수 있다.
const { data, refetch } = useQuery({
queryKey: ["posts"],
queryFn: fetchPosts,
});
return (
<button onClick={() => refetch()}>데이터 새로고침</button>
);
// PostList.js
const { data } = useQuery({ queryKey: ["posts"], queryFn: fetchPosts });
// PostDetail.js
const { data } = useQuery({ queryKey: ["posts"], queryFn: fetchPosts });
하나의 query key를 여러 곳에서 사용하면 캐싱된 데이터가 공유되고, 한곳에서 데이터가 갱신되면 모든 곳에 반영이 된다.
import { useQuery } from "@tanstack/react-query";
import axios from "axios";
const fetchPosts = async () => {
const { data } = await axios.get("https://jsonplaceholder.typicode.com/posts");
return data;
};
function Posts() {
const { data } = useQuery({
queryKey: ["posts"], // 캐싱 키 (중복 방지)
queryFn: fetchPosts, // API 요청 함수
});
return (
<ul>
{data.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
export default Posts;
첫번째 인자로 Unique Key를 포함한 배열이 할당된다.
배열의 첫 요소는 Unique Key가 되며, 두번째 요소부터는 query 함수 내부의 파라미터로 값들이 전달된다.
두번째 파라미터로 실제 호출되는 비동기 함수가 할당된다.
함수는 Promise를 반환하는 형태여야 한다.
최종 반환값은 API의 성공, 실패 여부, 반환값을 포함하는 객체이다.
enabled 옵션을 사용하면 비동기 함수인 useQuery를 동기적으로 사용 할 수 있다.
const { data:list1, error, isFetching } = useQuery({
queryKey: ["list1"],
queryFn: fetchPost1,
});
const { data:list2, error, isFetching } = useQuery({
queryKey: ["list2"],
queryFn: fetchPost2,
enabled: !!list1
});
enabled의 값이 true가 되면 fetchPost2가 동작한다.
여러개의 쿼리를 동시에 실행하고, 각각의 상태를 별도로 관리할 수 있게 해주는 훅이다.
여러개의 쿼리를 병렬로 실행하고 배열 형태로 결과를 반환한다.
import { useQueries } from "@tanstack/react-query";
import axios from "axios";
// 각 쿼리에서 데이터를 가져오는 함수들
const fetchPosts = async () => {
const { data } = await axios.get("https://jsonplaceholder.typicode.com/posts");
return data;
};
const fetchUsers = async () => {
const { data } = await axios.get("https://jsonplaceholder.typicode.com/users");
return data;
};
function MyComponent() {
const queries = useQueries([
{
queryKey: ["posts"],
queryFn: fetchPosts,
},
{
queryKey: ["users"],
queryFn: fetchUsers,
},
]);
// 쿼리 결과를 변수에 추출
const postsQuery = queries[0];
const usersQuery = queries[1];
// 데이터 로딩 처리
if (postsQuery.isLoading || usersQuery.isLoading) return <p>Loading...</p>;
// 에러 처리
if (postsQuery.isError || usersQuery.isError) return <p>Error occurred!</p>;
return (
<div>
<h3>Posts</h3>
<ul>
{postsQuery.data.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
<h3>Users</h3>
<ul>
{usersQuery.data.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}
export default MyComponent;
각각의 쿼리의 상태를 독립적으로 관리 할 수 있고, 여러개의 useQuery를 실행하는 것 보다 더욱 간결해져 관리하기 쉬워진다.
useMutation은 데이터를 수정하는 작업 (예: 생성, 업데이트, 삭제 등)을 처리할 때 사용하는 React Query의 훅으로, POST, PUT, DELETE 등의 HTTP 메서드를 사용한 요청에 사용된다.
import { useMutation } from "@tanstack/react-query";
import axios from "axios";
// 새로운 포스트를 생성하는 함수
const createPost = async (newPost) => {
const response = await axios.post("https://jsonplaceholder.typicode.com/posts", newPost);
return response.data;
};
function CreatePost() {
const mutation = useMutation(createPost, {
onSuccess: (data) => {
// 성공적으로 데이터를 생성한 후 실행할 코드
console.log("새로운 포스트가 생성되었습니다!", data);
},
onError: (error) => {
// 실패한 경우 실행할 코드
console.error("포스트 생성 실패:", error);
},
onSettled: () => {
// 성공 여부와 상관없이 항상 실행
console.log("요청이 완료되었습니다!");
},
onMutate: (variables) => {
console.log("뮤테이션이 시작되기 전에 호출됨", variables);
},
});
// 데이터가 로딩 중일 때 표시할 내용
if (mutation.isLoading) return <p>Loading...</p>;
// 오류 발생 시 표시할 내용
if (mutation.isError) return <p>Error occurred!</p>;
return (
<div>
<button
onClick={() => mutation.mutate({ title: "New Post", body: "This is a new post" })}
>
새 포스트 생성
</button>
</div>
);
}
export default CreatePost;
useQuery와 비슷하지만 첫 인자로 비동기 함수가, 두번째 인자로 분기처리가 할당된다는 점이 다르다.
mutation을 통해 각 상태 처리 또는 값에 대한 접근이 가능하다.
서버에서 데이터를 수정 한 후 쿼리 데이터가 변경되어야 하는 경우가 있다.
이러한 경우 invalidateQueries를 사용하면 변경된 내용을 반영한 최신 데이터로 업데이트 할 수 있다.
const queryClient = useQueryClient();
const mutation = useMutation(createPost, {
onSuccess: (data) => {
// 새 포스트 생성 후 'posts' 쿼리를 무효화하여 최신 데이터를 가져오도록 설정
queryClient.invalidateQueries(["posts"]);
},
});