일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- java
- 자바문제풀이
- 반응형 페이지
- Servlet
- CSS
- 변수
- github
- git
- TypeScript
- react
- react-router-dom
- webpack
- 자바
- 그럼에도불구하고
- redux
- JavaScript
- JS
- max-width
- HTML
- 코드업
- node
- 그럼에도 불구하고
- @media
- coding
- frontend
- cleancode
- 코딩테스트
- 프론트엔드
- node.js
- media query
- Today
- Total
그럼에도 불구하고
[React Query] isLoading과 isFetching은 헷갈려 본문
React Query를 공부하다 보면 isLoading과 isFetching에 대해 그냥 지나칠 수 없는데
너무 헷갈려서 정리해보기로 했습니다.
🧑🏻💻 isLoading이 끝나지 않는 경우
button을 눌렀을 때 react query를 사용하여 비동기 방식으로 data를 가져올 건데, enabled:false 이면 isLoading이 계속 true인 문제가 발생했습니다. ( button을 누르기 전부터 isLoading 상태임 )
즉, button을 눌렀다면 enabled: true 가 되어 해당 query의 isLoading이 true에서 false로 변경되고 UI가 보여야 하는 상황인데 보이지가 않았습니다.
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의 status와 fetchStatus에 대해 알아보자.
📌 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
📌 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
📌 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 refetch와 stale-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>;
}
'React > React query' 카테고리의 다른 글
[React Query] 페이지네이션(Pagination)과 데이터 프리페칭(Prefetching) (1) | 2023.10.18 |
---|---|
[React Query] React Query를 TypeScript로 사용할 때 Type 지정 (0) | 2023.10.14 |
[React Query] useInfiniteQuery와 Infinite Scroll (0) | 2023.08.30 |
[React Query] React Query에 대하여 (0) | 2023.08.24 |