react-query v3.39.2 기준
부모 컴포넌트와 자식의 자식 컴포넌트에서 사용했다.
부모 컴포넌트에서 useQuery
로 필요한 데이터를 받고 자식의 자식 컴포넌트에서 그 데이터가 필요했다. 세 가지 방법이 생각났다.
useQuery
를 자식의 자식 컴포넌트에서 사용하기첫 번째 방법은 depth가 2개라서 번거롭다. 두 번째 방법은 atom
을 만들고 상태값을 set해줘야 하기 때문에 번거롭다. 마지막 방법은 애초에 캐싱을 한고 그걸 이용한다면 리소스 낭비가 적을 것 같고 컴포넌트 관리측면에서 위의 두 가지 방법보다 좋다. 그래서 두 번 사용하기로 했다.
useQuery
에서 fetch가 성공하는 경우에는 문제가 없었다. 그러나 실패한 경우 문제가 발생했다. 무한 렌더링이 발생했다.
아래는 문제를 발생시킨 간단한 예제다. 부모 컴포넌트와 자식 컴포넌트에서 useQuery
를 사용했다. 실무에서 작성한 코드는 부모와 자식의 자식이지만 문제가 발생하는 원인은 같아 좀 더 이해하기 쉬운 예제로 작성했다.
문제의 원인은 에러가 나면 isLoading 상태를 저장하지 않기 때문이다. 이거와 맞물려 부모 컴포넌트에서 로딩처리하는 부분이 있으면 무한 렌더링이 발생한다.
이 4번재 순간에 자식 컴포넌트에서 isLoading
이 true
가 되면 부모 컴포넌트에 있는 isLoading
도 true
로 갱신된다. 그러면 다시 위의 과정 반복이 일어난다. 부모 컴포넌트에서 에러가 발생하고 isLoading
은 false
가 되면 자식 컴포넌트가 또 렌더링 되서 자식 컴포넌트 isLoading
은 true
가 된다. 여기서 무한 굴레에 빠지게 된다.
해결방법은 두가지가 있다.
import Child from "./Child";
import useCustomQuery from "./useCustomQuery";
export default function Parent() {
const { data, isLoading, isError } = useCustomQuery();
console.log("Parent", isLoading);
console.log(isError);
if (isLoading) return <div>Loading...</div>;
if (isError) return null; // 에러처리
return (
<div>
<h1>Parent</h1>
<Child />
</div>
);
}
이 방법은 useQuery
에 onError로 에러 처리를 할 경우 onError 에러 처리와 isError로 에러 처리를 해야 하므로 두 번 신경써야 하기 때문에 좋지 않은 방법이다.
import useCustomQuery from "./useCustomQuery";
export default function Child() {
const queryClient = useQueryClient();
const data = queryClient.getQueryData("getData");
return (
<div>
<h1>Child</h1>
</div>
);
}
useQueryClient
를 이용하면 캐싱된 데이터를 불러올 수 있다. useQuery
를 두 번 호출하지 않는 방법이다. 깔끔해서 좋다.
useQuery
에 isLoading
은 최초로 fetch가 되면 일반적으로 변하지 않는다. 한 번 true → false
로 되면 다음에는 똑같은 useQuery
를 호출하면 isLoading
은 false
이고 isFetching
은 true → false
로 변한다. cacheTime: 0
를 설정하거나 queryClient.invalidateQueries(key)
사용해도 마찬가지다. 얼마동안 시간만큼 isLoading 상태를 캐싱하는 지는 모르겠다.
최초로 useQuery
를 호출하고 그 뒤에 똑같은 useQuery
를 호출했을 때 isLoading
이 변하는 경우는 두 가지다. fetch할 때 에러가 난 경우와 queryClient.clear()
를 실행한 경우다. 다만, queryClient.clear()는 모든 캐시를 지우기 때문에 주의가 필요하다.