그럼에도 불구하고

👨‍💻

[React Query] isLoading과 isFetching은 헷갈려 본문

React/React query

[React Query] isLoading과 isFetching은 헷갈려

zenghyun 2023. 10. 19. 11:17

 

 

React Query를 공부하다 보면 isLoading과 isFetching에 대해 그냥 지나칠 수 없는데

너무 헷갈려서 정리해보기로 했습니다.  

 

 

🧑🏻‍💻 isLoading이 끝나지 않는 경우 

button을 눌렀을 때  react query를 사용하여 비동기 방식으로 data를 가져올 건데, enabled:false 이면 isLoading이 계속 true인 문제가 발생했습니다. ( button을 누르기 전부터 isLoading 상태임 )

 

즉, button을 눌렀다면 enabled: true 가 되어 해당 query의 isLoading이 true에서 false로 변경되고 UI가 보여야 하는 상황인데 보이지가 않았습니다. 

 

하염없이 기다려도 무한 Loading..

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import { useCallback } from "react";
import { useQuery } from "@tanstack/react-query";
import axios from "axios";
 
const fetchSuperHeroes = async () => {
  return await axios.get("http://localhost:4000/superheroes");
};
export const RQSuperHeroesPage = () => {
  const {
    isLoading,
    data,
    isError,
    error,
    refetch,
  } = useQuery({
    queryKey: ["super-heroes"],
    queryFn: fetchSuperHeroes,
    enabled: false,
  });
 
  const handleClickRefetch = useCallback(() => {
    refetch();
  }, [refetch]);
 
  if (isLoading) {
    return <h2>Loading...</h2>;
  }
 
  if (isError) {
    return <h2>{error.message}</h2>;
  }
 
  return (
    <>
      <h2>RQ Super Heroes Page</h2>
      <button onClick={handleClickRefetch}>Fetch Heroes</button>
      <ul>
        {data?.data.map((hero) => {
          return <li key={hero.name}>{hero.name}</li>;
        })}
      </ul>
    </>
  );
};
 
cs

 

왜 계속 Loading 상태가 지속되는걸까...

우선 useQuery의 statusfetchStatus에 대해 알아보자.

 

📌 useQuery는 무엇을 리턴하는가?

1
2
3
4
5
6
7
8
const { status, isLoading, isError, error, data, isFetching, ... } = useQuery({
queryKey: ["colors", pageNum],
queryFn: () => fetchColors(pageNum)
}
);
 
 
 
cs

 

 

  • status: 쿼리 요청 함수의 상태를 표현하는 status는 4가지의 값이 존재합니다. 
    • idle : 쿼리 데이터가 없고 비어있을 때,  { enabled: false }  상태로 쿼리가 호출되면 이 상태로 시작합니다.
    • loading : 아직 캐시된 데이터가 없고 로딩중일 때의 상태입니다.
    • error : 요청 에러가 발생했을 때의 상태입니다.
    • success : 요청 성공했을 때의 상태입니다.
  • data: 쿼리 함수가 리턴한 Promise에서 resolved 된 데이터를 말합니다. 
  • isLoading : 캐싱된 데이터가 없을 때 👉 즉, 처음 실행된 쿼리일 때 로딩 여부에 따라 true/false를 반환합니다.
    • 이는 캐싱된 데이터가 있다면 로딩 여부에 상관없이 false를 반환합니다.
  • isFetching : 캐싱된 데이터가 있더라도 쿼리가 실행되면 로딩 여부에 따라 true/false를 반환합니다. 
    • 이는 캐싱된 데이터가 있더라도 쿼리 로딩 여부에 따라 true/false를 반환합니다.
  • error : 쿼리 함수에 오류가 발생한 경우, 쿼리에 대한 오류 객체를 반환합니다.
  • isError : 에러가 발생한 경우 true 상태입니다.
  • 그 외의 여러가지의 반환 데이터들이 존재합니다. 

 

useQuery 공식 사이트 문서

https://tanstack.com/query/v4/docs/react/reference/useQuery

 

useQuery | TanStack Query Docs

const { data,

tanstack.com

 

📌 Tanstack Query (React Query v4) 

Tanstack Query(v4) 부터는 status의 idle이 제거되고, 새로운 상태값인 fetchStatus가 추가되었습니다.

 

⭐️ fetchStatus

  • fetching : 쿼리가 현재 실행중입니다.
  • paused : 쿼리를 요청했지만, 잠시 중단된 상태입니다.
  • idle : 쿼리가 현재 아무 작업도 수행하지 않는 상태입니다.

 

📌 v4부터 status와 fetchStatus를 나누는 이유 

  • fetchStatus는 HTTP 네트워크 연결 상태와 좀 더 관련된 상태 데이터입니다.
    • 예를 들어, status가 success 상태라면 주로 fetchStatus는 idle 상태지만, 백그라운드에서 re-fetch가 발생할 때 fetching 상태일 수 있습니다.
    • status가 보통 loading 상태일 때 fetchStatus는 주로 fetching를 갖지만, 네트워크 연결이 되어 있지 않은 경우 paused 상태를 가질 수 있습니다.
  • 정리하자면 아래와 같습니다.
    • status는 data가 있는지 없는지에 대한 상태를 의미합니다.
    • fetchStatus는 쿼리 즉, queryFn 요청이 진행 중인지 아닌지에 대한 상태를 의미합니다.

자세한 얘기는 아래를 참고하자 :)

 

https://tanstack.com/query/v4/docs/react/guides/queries#why-two-different-states

 

Queries | TanStack Query Docs

Query Basics A query is a declarative dependency on an asynchronous source of data that is tied to a unique key. A query can be used with any Promise based method (including GET and POST methods) to fetch data from a server. If your method modifies data on

tanstack.com

 

📌 Why two different states?

"isLoading or status === 'loading' - The query has no data yet"

공식문서를 읽어보면, isLoading은 쿼리가 아무 데이터도 캐싱하고 있지 않은 경우를 나타낸다고 생각할 수 있습니다.

enabled: false 인 경우 쿼리를 보내지 않았으므로 데이터가 비어있게 되고, isLoading이 true로 남아있는 것이 의도된 작동으로 보입니다.

 

즉, 처음 의도한 것처럼 button을 눌렀을 때 데이터를 요청하고, 이를 기다리는 중인지를 알려면 isFetching을 사용해야 합니다.

 

The status gives information about the data : Do we have any or not? 
The fetchStatus gives information about the queryFn : Is it running or not?

 

쿼리가 괜찮은? (요청에 성공해서 받아온) 데이터를 들고 있는지는 status로, 쿼리의 요청 상태는 fetchStatus로 알 수 있는 것입니다.

 

📌 Stale While Revalidate

background refetchstale-while-revalidate는 별개의 두 가지 feature 가 아닙니다.

React Query는 데이터를 캐싱하고, 해당 데이터가 필요할 때 더 이상 최신 상태가 아니더라도(stale) 데이터를 제공합니다.

 

"오래된 데이터가 없는 것보다는 낫다는 것"

 

데이터가 없다는 것은 대개 Loading Spinner의 표시를 의미하며, 이는 사용자 경험 (User Experience)에서 안 좋은 영향을 미치기 때문입니다.

 

따라서 오래된 데이터를 보여줌과 동시에 background refetch를 수행하여 해당 데이터를 다시 검증(stale-while-revalidate) 합니다.

  • 예를 들어, 현재 fetching에 성공한 올바른 데이터가 캐싱되어 있는 경우는 오직 status === success 일 때뿐입니다. 즉, isLoading 이 true 인 경우( status === loading ) 에는 쿼리에 아무 데이터도 없는 상황으로 해석됩니다.
  • 이는 데이터가 stale 한 것과는 다릅니다. 가장 최근의 query가 성공해서 데이터를 들고 있다면, status는 success 상태여야 하기 때문입니다. ( status는 데이터의 상태이기 때문 )
  • 그런데 데이터를 보여주고 있는 와중에 해당 데이터가 stale 한 지 revalidate를 수행하고 싶다면 어떻게 해야 할까요? (background refetch)
  • status 하나만으로 관리하는 경우, 이 refetch에 대해서도 status === loading으로 변경되어야 할 것입니다.
  • 그러면 기껏 캐싱된 데이터를 들고 있는 의미가 사라진다. 어차피 loading 인 동안에는 일반적으로 데이터가 없는 것으로 간주하고 로딩 UI를 보여주기 때문입니다.
  • 즉 데이터가 아예 없는 경우와, 데이터가 있지만 오래되어 유효한지 검증 중인 경우를 구분할 수 없게 됩니다.
  • 따라서 background refetch 포함해 쿼리의 요청 상태를 나타내기 위한 fetchStatus 필요합니다. status(loading) 사용자에게 보여줄 있는 멀쩡한 데이터를 들고 있는지 판단하고, fetchStatus(fetching) 데이터 업데이트를 시도 중인지 판단할 있게 됩니다.

📌 결론

status는 데이터의 상태를 나타내고, fetchStatus는 쿼리의 요청 상태로 이해하여 loading과 fetching은 각각 위 상태의 일부라고 생각하는 게 좋을 것 같습니다.

 

stale 한 데이터를 갱신하는 요청은 isFetching을 사용하고 아직 호출된 적이 없거나, cacheTime이 종료되었거나 등 캐시가 없는 쿼리의 요청은 isLoading && isFetching을 사용해야 할 것 같습니다.

 

  if (isLoading && isFetching) {
    return <h2>Loading...</h2>;
  }

 

제대로 작동한다~

 

 

 

Comments