React Query에서 isLoading과 isFetching의 차이점은 무엇인가요? 언제 어느 쪽을 사용하고 다른 쪽을 사용하지 않을까요? 이를 파악하여 사용자에게 멋진 로딩 스피너를 보여주세요.
useQuery의 반환값에서 isLoading과 isFetching을 모두 얻을 수 있으며, 두 상태 모두 어떤 종류의 fetching이 발생하고 있음을 알 수 있습니다. 즉 서버의 상태를 가져오고 있습니다.
차이점이 무엇인지, 왜 이 두가지 상태가 모두 필요한지 궁금하실 것입니다. 데이터를 로드하는 건가요, 아니면 Fetching하는 것인가요? 둘 다 같은 의미 아닐까요?
하지만 React Query를 사용하는 경우 대부분의 쿼리에는 두 가지가 모두 필요합니다. 사용자에게 일어나는 일을 UI에 표시하는 방식이 isLoading만 참이면 달라지기 때문입니다.
isLoading은 일반적으로 쿼리가 처음 fetching 할 때 참이기 때문입니다. 처음 및 백그라운드 fetch를 포함하여 쿼리가 가져올 때마다 참인 isFetching과 다르게 말이죠.
// fetching for the first time
{
data: undefined,
isLoading: true,
isFetching: true
}
// fetching again
{
data: [ { img: "https://images.yourdomain.com/img-6473824326.heic", name: "IMG_5463.HEIC", date: .... }, ... ],
isLoading: false,
isFetching: true
}
이제 차이점을 살펴보고, isFetching과 비교하여 isLoading을 처리하는 방법을 살펴보겠습니다. 또한 isRefetching이 어떻게 편리한 단축키를 제공하는지 볼 것입니다.
isLoading in React Query
캐시에 데이터가 없을 때 isLoading은 참입니다. status === "loading"과 동일하며 쿼리가 아직 데이터를 가져오지 않았음을 의미합니다. 쿼리를 처음 실행하면 캐시에 아직 데이터가 없습니다. 쿼리가 이전에 데이터를 가져온 적이 없으므로 표시할 저장된 데이터가 없습니다.
우리가 사진앱을 만들고 '호수' 앨범을 본다고 가정해봅시다.
import { useQuery } from '@tanstack/react-query'
function Album(props) {
const { data, isLoading, status } = useQuery({ queryKey: ["album", "lakes"], queryFn: () => fetchAlbum("lakes") });
console.log({ data, isLoading, status });
// {
// data: undefined,
// isLoading: true,
// status: "loading"
// }
};
쿼리는 loading 중이고 isLoading은 true입니다.
isLoading의 좋은 사용 사례는 데이터를 처음 가져올 때 페이지에 로딩 스피너를 표시하는 것입니다. 사용자가 어떤 일이 일어나고 있으며 필요한 정보를 로드하고 있음을 알 수 있습니다.
isFetching in React Query
isFetching은 가져올 때마다 참입니다. 여기에는 첫 번째 fetch 및 추가 fetch도 포함됩니다.
우리는 이미 "호수" 앨범을 가져왔고, 그 사진들은 멋지게 보입니다. 하지만 호수 사진을 멋지게 찍는 친구들이 몇 명 있다면 어떨까요?
이들과 앨범을 공유하면 새 사진이 추가되면 앨범이 업데이트되는 것이 좋을 것입니다. 데이터가 오래되었거나 무효화되었기 때문에 데이터를 다시 가져와야 할 수 있습니다.
다행히도 React Query의 staleTime을 이용하면 애플리케이션이 서버에서 업데이트를 확인하는 빈도를 선택할 수 있습니다. 하지만 이미 이미지가 표시되어 있으므로 전체 페이지 로딩 디스플레이를 다시 깜빡이는 대신 새 이미지를 확인하는 동안 현재 이미지를 계속 표시하는 것이 좋을 것입니다.
이렇게 하면 사용자는 정보가 변경되었음을 알 수 있지만 그 정보를 계속 볼 수 있습니다.
How to use isLoading and isFetching
이제 함께 연결하여 표시할 데이터가 없을 때는 isLoading 상태를 표시하고, 이미 앨범을 표시한 후 fetching중일 때는 isFetching을 표시하도록 하겠습니다.
import { useQuery } from '@tanstack/react-query'
import Loading from './Loading';
import Photos from '/Photos';
import Alert from '/Alert';
function Album({ id }) {
const albumQuery = useQuery({ queryKey: ["album", id], queryFn: () => fetchAlbum(id) });
// data is fetched
if (albumQuery.data) {
<div>
{ albumQuery.isFetching && <Loading type="spinner" /> }
<h1>{albumQuery.data.title}</h1>
<Photos data={albumQuery.data.photos} />
</div>
}
// error fetching
if (albumQuery.isError) {
return <Alert message={albumQuery.error.message} />
}
// loading by default if no data and no error
return <Loading message="Loading Photos" />;
};
위의 예시에서 실제로는 isLoading을 사용하지 않고 데이터도 없고 오류도 없는 것으로 가정한 것을 알 수 있습니다. 저는 로딩을 폴백으로 사용하는 이 패턴을 정말 좋아하는데, 데이터나 오류가 있는 경우 해당 정보를 표시하는 것이 더 중요하기 때문입니다. 이 패턴은 TkDodo가 React Query의 상태 검사에서 소개한 것입니다.
로딩부터 데이터 표시, 백그라운드에서 새 데이터 불러오기까지 전체 프로세스는 다음과 같습니다.
isRefetching in React Query
isRefetching은 쿼리가 fetching 중일 때 참입니다(처음은 포함하지 않고).
이미 isLoading에 대한 로딩 페이지를 표시하고 있으므로 추가적인 isFetching 스피너를 동시에 표시하고 싶지 않을 수 있습니다.
위의 예시에서는 데이터가 있을 때만 isFetching 스피너를 표시하므로 isLoading이 동시에 참이 되지 않으므로 상관없습니다. 하지만 어떤 이유로든 이 흐름이 작동하지 않는다면 다음과 같이 백그라운드 가져오기인지 확인할 수 있습니다.
let isBackgroundFetch = isFetching && !isLoading;
그리고 React Query는 isRefetching으로 똑같은 결과를 제공하기 때문에 변수를 직접 만들 필요도 없습니다. 좋죠!
import { useQuery } from '@tanstack/react-query'
function Album(props) {
const { data, isLoading, isFetching, isRefetching } = useQuery({ queryKey: ["album", "lakes"], queryFn: () => fetchAlbum("lakes") });
// first fetch
console.log({ data, isLoading, isFetching, isRefetching });
// {
// data: undefined,
// isLoading: true,
// isFetching: true,
// isRefetching: false
// }
// next fetch
console.log({ data, isLoading, isFetching, isRefetching });
// {
// data: { ... },
// isLoading: false,
// isFetching: true,
// isRefetching: true
// }
};
앨범 예제에서는 다음과 같은 작업을 수행할 수 있습니다:
import { useQuery } from '@tanstack/react-query'
import Loading from './Loading';
import Photos from '/Photos';
import Alert from '/Alert';
function Album({ id }) {
const albumQuery = useQuery({ queryKey: ["album", id], queryFn: () => fetchAlbum(id) });
return (
<div>
{ albumQuery.isRefetching && <Loading type="spinner" /> }
{ albumQuery.data && <h1>{albumQuery.data.title}</h1> }
{ albumQuery.isError && <Alert message={albumQuery.error.message} /> }
{ albumQuery.data && <Photos data={albumQuery.data.photos} /> }
{ albumQuery.isLoading && <Loading message="Loading Photos" /> }
</div>
);
};