5. React Query의 Status
1. 쿼리 상태와 페치 상태
React Query는 현재 상태를 알려주는 플래그를 제공한다.
내부 상태 머신에서 파생된 여러가지 boolean 플래그 값을 제공하는데,
선언된 타입을 보면 `쿼리`상태, `페치`상태가 각각 3가지씩 존재한다.
- `쿼리`상태 : 데이터(캐시)에 대한 정보
- `success`: 쿼리가 성공했고, 이에 대한 `data`가 쿼리캐시에 설정된다.
- `error`: 쿼리가 실패했고, `error`가 설정된다.
- `pending`: 쿼리캐시에 데이터가 없다.
- `페치`상태: Fn에 대한 정보
- `fetching`: `queryFn` 요청이 실행중이고 데이터를 기다리고 있다.
- `paused`: `queryFn`, `mutationFn` 이 실행중이지 않고 다시 인터넷에 연결될 때 까지 일시중지된 상태이다.(offline)
- `idle`: `~Fn`이 실행중이 아니다.
쿼리상태와 페치상태는 서로 독립적이다.
또한 각 상태 내부에서는 하나의 상태만 가질 수 있다.
즉 `fetching`이면서 `success`일 수 있고, `fetching`이면서 `error`일 수 있지만,
`pending` 이면서 `success` 일 수는 없다!
위는 내부 상태 머신에서 파생된 여러가지 boolean 플래그 값이다.
캐시 상태정보인 쿼리상태와 함수 상태정보인 페치상태를 잘 알고있다면 적절히 써먹을 수 있다.
참고로 `isLoading` 은 `isPending && isFetching`을 의미한다. (캐시가 없고, 요청중)
즉 그 쿼리의 첫번째 `fethcing` 이라는 의미이다.
2. 표준 예시
const todos = useTodos()
if (todos.isPending) {
return 'Loading...'
}
if (todos.error) {
return 'An error has occurred: ' + todos.error.message
}
return <div>{todos.data.map(renderTodo)}</div>
위 예시는 쿼리가 요청중인지, 에러인지를 순서대로 확인하고 데이터를 렌더링한다.
대부분의 경우 괜찮지만 React Query가 제공하는 백그라운드 페칭(refetchOnMount, refetchOnWindowFocus 그리고 refetchOnReconnect) 때문에 UX가 나빠질 수 있다.
어떤 문제가 발생하는지 알아보자.
3. 백그라운드 에러
많은 상황에서 백그라운드 페칭이 실패하면 조용히 무시될 수 있지만, 위 코드에서는 불가능하다.
일어날 수 있는 상황을 보자.
- 사용자가 페이지를 열고 첫번째 쿼리가 성공한다. 이후 잠시동안 다른 탭에서 작업하다가 페이지로 다시 돌아오면 React Query가 백그라운드 페칭을 한다. 그리고 페칭이 실패한다.
- 사용자가 아이템 목록 페이지에 들어온다. 아이템 하나를 골라 클릭한다. 쿼리가 성공하고 목록 페이지로 돌아온다. 이제 다시 그 아이템 페이지로 들어가면 캐시에 남아있는 데이터를 볼 것이다. 하지만 이때 백그라운드 페칭이 실패한다.
위 두 상황에서 쿼리의 상태는 다음과 같다.
{
"status": "error",
"error": { "message": "Something went wrong" },
"data": [{ ... }]
}
보다시피 사용 가능한 오래된 데이터와 에러가 공존한다.
이는 오래된 캐싱을 사용하면서 유효성을 다시 검사하는 (stale-while-revalidate caching) 메커니즘을 이용하기 때문이다.
즉 오래된 데이터라 할지라도, 존재한다면 보여준다.
이제 다시 표준예시를 살펴보면..
const todos = useTodos()
if (todos.isPending) {
return 'Loading...'
}
// 캐시가 남아있지만 백그라운드 페칭 때 에러가 발생한다.
if (todos.error) {
return 'An error has occurred: ' + todos.error.message
}
return <div>{todos.data.map(renderTodo)}</div>
캐시가 남아있지만 백그라운드 페칭 때 데이터 대신 에러가 렌더링되는 문제가 발생한다.
이제 어떤 것을 우선해서 보여줄지는 우리에게 달려있다.
에러를 보여주는게 중요할까? 오래된 데이터가 있는 경우 데이터만 표시하는 것으로 충분할까?
아니면 둘 다 표시하고 에러는 토스트로 보여주면 어떨까?
구현은 요구사항에 따라 달라질 것이므로 이 질문에 대한 명확한 답변은 없다.
하지만 위 두가지 예시를 고려할때 렌더링 되어있는 캐시 데이터가 갑자기 에러 화면으로 바뀐다면 혼란스러운 사용자 경험을 줄 수 있을 것 같다.
React Query가 실패한 쿼리는 세번까지 다시 시도하므로, 에러 화면으로 대체될 때까지 몇 초가 걸릴 수 있기 때문에 더 문제가 될 수도 있다. 사용자는 그 몇 초동안 아무런 피드백을 받을 수 없기 때문이다.
이러한 이유로 캐시 데이터를 먼저 확인하는 방법이 있다.
const todos = useTodos()
if (todos.data) {
return <div>{todos.data.map(renderTodo)}</div>
}
if (todos.error) {
return 'An error has occurred: ' + todos.error.message
}
return 'Loading...'
다시 말하지만 구현은 요구사항에 따라 달라지기 때문에 "옳은 방법"은 없다.
다만 개발자로서 React Query의 공격적인 리페칭이 가져올 수 있는 결과를 알고있어야한다.
Reference
https://tanstack.com/query/latest/docs/reference/QueryClient#queryclientisfetching
https://tkdodo.eu/blog/status-checks-in-react-query