- 유데미 강의 React Query / TanStack Query : React로 서버 상태 관리하기 강의를 참고하여 작성한 글입니다.
- Tanstack Query v5 버전을 기준으로 작성하였습니다.
import { useQuery } from "@tanstack/react-query";
// 쿼리 함수
function fetchPosts() {
const response = await fetch(`https://jsonplaceholder.typicode.com/posts?_limit=10`);
return response.json();
}
/*
- data : 서버로 부터 가져온 값, 쿼리가 성공적으로 완료 되었을 때 사용
- isLoading : 실행중인 쿼리 로딩 상태 boolean 값
- error : 쿼리 실행 중 발생한 오류를 담고 있음, 오류가 없을 경우 null
- isError : 쿼리 오류 상태 boolean 값
- queryKey : 쿼리의 고유 식별자로서 캐싱, 업데이트, 무효화 등의 작업에 쓰임. 배열 형태
- queryFn : 쿼리 함수
- 기타 옵션 인자 : 커스터마이즈를 위한 여러 옵션 (staleTime, retry, enabled 등)
*/
const { data, isLoading, error, isError } = useQuery({
queryKey: ["posts"],
queryFn: fetchPosts,
retry: 2, // 실패 시 재시도 횟수
refetchOnWindowFocus: false // 윈도우 포커스 시 자동 재조회 비활성화
});
// 로딩 상태일 때
if (isLoading) return <div>Loading...</div>;
// 에러 발생 시
if (isError) return <div>Error: {error.message}</div>;
console.log(data);
const { data, isLoading, isError, error } = useQuery({
queryKey: ["comments", id],
queryFn: () => fetchComments(id),
});
queryFn가 어떤 변수에 의존적이라면 그 변수는 쿼리 키에 포함되어야 함
만약 queryFn가 변수에 의존적이라면, 그 변수의 값이 바뀔 때마다 다른 데이터를 요청하게 되기 때문에 유동적으로 바뀌는 변수인 경우 새로운 데이터를 올바르게 요청하고 캐싱할 수 있도록 해야 함
Promise
를 리턴queryKey
에 포함 되어야 함queryFn
내부에서 발생하는 에러는 자동으로 캐치되어 error
상태로 관리fetch 진행 상태를 표현할 때, 캐싱된 값이 있어야 하는지에 대한 여부에 따라 isFetching, isLoading을 사용하면 됨
queryKey
를 가진 쿼리는 캐싱된 데이터를 재사용함placeholderData
인자는 useQueries
에도 존재하지만 useQuery
처럼 이전 렌더된 쿼리들에서 정보를 넘겨 받을 수 있는건 아님placeholderData
- 해당 데이터는 서버 데이터와 관련 없는 보여주기용 가짜 데이터
- useQuery로 가져오는 동안 해당 데이터를 보여주기 위해 사용 (Loading용 데이터라 생각하면 됨)
import { useQueries } from '@tanstack/react-query';
const fetchUser = async (userId: number) => {
const response = await fetch(`https://api.example.com/users/${userId}`);
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
};
const fetchPosts = async (userId: number) => {
const response = await fetch(`https://api.example.com/users/${userId}/posts`);
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
};
const UserProfile = ({ userId }: { userId: number }) => {
const results = useQueries([
{
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
},
{
queryKey: ['posts', userId],
queryFn: () => fetchPosts(userId),
},
]);
const userQuery = results[0];
const postsQuery = results[1];
if (userQuery.isLoading || postsQuery.isLoading) return <div>Loading...</div>;
if (userQuery.error) return <div>Error loading user: {userQuery.error.message}</div>;
if (postsQuery.error) return <div>Error loading posts: {postsQuery.error.message}</div>;
return (
<div>
<h1>{userQuery.data.name}</h1>
<ul>
{postsQuery.data.map((post: any) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
};
export default UserProfile;