이 글은 다음 블로그 글을 일부 번역한 글입니다.
리액트 쿼리는 오프라인 시나리오를 처리하기 위해 매우 잘 갖춰진(well-equipped) 도구이다. 리액트 쿼리는 캐싱 레이어를 제공하기 때문에, 캐시가 채워져 있는 동안, 네트워크 연결이 되어 있지 않아도 계속 작업을 할 수 있다. 버전 3에서 우리가 예상한데로 동작하지 않은 edge 케이스를 살펴보자.
말했듯이 캐시가 채워져 있는 동안 버전 3에서는 잘 동작한다. 그러나 동작이 이상한 케이스는 다음과 같다.
이 상황에서 벌어지는 일은 리액트 쿼리가 다시 연결될 때까지 계속 loading 상태로 남아있는 것이다. 브라우저 개발자 도구에서 실패한 네트워크 요청을 볼 수 있다. 이건 리액트 쿼리가 처음 요청을 보내고 실패했는데, 당신이 네트워크 연결이 없을 경우 다시 요청하지 않기 때문이다.
더 나아가, 리액트 쿼리 Devtools는 당신의 쿼리가 fetching 상태라고 보여주는데, 이건 항상 옳은건 아니다. 실제 쿼리는 pause 된 상태인데, 이 상태를 나타낼 컨셉이 없기 때문이다.
마찬가지로 위에서 만약 재요청 옵션을 꺼버린다면, 쿼리는 곧바로 error 상태로 갈 것이다. 왜 우리는 네트워크 연결이 없는 상태에서 쿼리 요청을 중단하기 위해서 retries 옵션이 왜 필요한걸까? (즉, paused 인 상태를 표현할 수 있는 방법이 없을까?)
import { useQuery } from 'react-query'
const result = useQuery(['todos', 1], fetchTodoListPage, {
retry: false,
})
(v3에서) 네트워크가 필요없는 쿼리는 어떤 이유로 요청이 실패할 경우, (네트워크가 필요없다면 그럴 필요가 없음에도) 다시 네트워크가 연결 될 때까지 멈춘다. 따라서 이런 쿼리들은 윈도우 포커스가 될 때 동작하지 않는다. 왜냐하면 이런 쿼리의 기능은 네트워크 연결이 없다면 완전히 불가능하기 때문이다.
요약하자면 여기에는 두 가지 주요 문제가 있다. 어떤 경우에 리액트 쿼리는 네트워크 연결이 필요 없는데도 필요 있다고 가정하고(네트워크가 필요없지만 필요할 때까지 기다리거나), 어떤 경우는 쿼리를 요청하면 안 되는 경우에 요청해버린다(현재 네트워크가 없는 상태인데 바로 쿼리를 요청해버림).
v4에서는 이런 문제를 새로운 networkmode
세팅으로 제동을 걸고자 한다. 이 세팅으로, 우리는 online과 offline 쿼리를 명확히 구분할 수 있다. 이 옵션은 useQuery 뿐만 아니라 useMutation도 가능하며, 해당 옵션을 전역 또는 개별 쿼리별로 세팅할 수 있다는 의미이다.
v4에서 해당 상태가 새로운 기본 모드이다. 즉, 우리는 리액트 쿼리가 오직 활성화된 네트워크 연결 상태일 때만 동작한다고 가정한다.(= 네트워크가 연결되지 않을 경우에 queryFn이 실행되지 않는다)
그렇다면 네트워크가 없는데 연결이 필요한 쿼리가 동작할 때는 어떻게 될까? 쿼리는 새로운 paused
상태가 된다. paused
상태는 쿼리의 메인 상태(status
: loading
, success
, error
)의 부차적인 상태이다. 왜냐하면 당신의 네트워크는 언제든 연결이 끊어질 수 있기 때문이다.
import { useQuery } from 'react-query'
const { status } = useQuery(['todos', 1], fetchTodoListPage)
// status : loading, success, error
이건 쿼리가 success
이고 paused
상태가 될 수 있다는 의미이다. 예를 들어, 당신이 처음에 데이터를 성공적으로 불러왔지만 네트워크가 유실 될 경우 fetchStatus
는 paused
가 된다.
혹은 loading
이고 바로 paused
가 될 수도 있다 (mount 되어서 queryFn이 실행되는 중간에 네트워크가 유실 될 경우)
따라서 로딩 스피너를 보여주기 위한 조건으로 'loading'만 체크하는 건 불충분하다. 쿼리는
state:'loaindg'
이지만,fetchStatus:'paused'
일 수 있다.
import { useQuery } from 'react-query'
const { fetchStatus } = useQuery(['todos', 1], fetchTodoListPage)
// status : fetching, paused, idle
우리는 쿼리가 동작하는 중임을 알리는 isFetching
플래그를 가지고 있었다. 비슷하게 paused
상태에서도, 쿼리는 success
와 fetching
이거나 error
와 fetching
일 수 있다.
fetching
과 paused
상태는 상호 배타적이기 때문에, 우리는 이들을 합쳐서 useQuery로부터 불러올 수 있는 새로운 fetchStatus
를 만들었다.
경험에 비추어, status
는 data에 대한 정보를 제공한다(success
는 데이터가 존재한다는 걸 의미하고, loading
은 아직 데이터가 없다는 뜻이다)
다른 말로 말하면, fetchStatus
는 queryFn
에 대한 정보를 제공한다(현재 동작하고 있는가 아닌가?)
isFetching
과isPaused
는 이 상태에서 파생된 것이다.
해당 모드에서는 리액트 쿼리가 네트워커 연결 여부를 신경쓰지 않는다. 항상 쿼리 함수가 실행되고 절대 paused
상태가 되지 않는다. 이 경우에서는 실제 네트워크 연결이 필요없는 환경에서는 리액트 쿼리를 실행하고 싶을 경우 사용할 수 있다.(e.g AsyncStorage
를 읽거나, Promise.resolve(5)
를 queryFn
으로부터 반환받기를 원하거나)
queryFn
이 실패할 경우, paused 가 아닌 error 상태가 바로 됨 refetchOnReconnection
옵션 설정 값이 false로 설정됨. 이 모드는 v3의 리액트 쿼리가 동작하는 방식과 유사하다. 처음 queryFn
은 (네트워크 연결 여부 상관 없이) 항상 실행 되고, 만약 실패할 경우 paused
상태가 된다. 이 모드의 경우는 리액트 쿼리 상위의 브라우저 캐시와 같은 추가적인 캐싱을 사용하는 경우 유용하다. 브라우저 캐시를 사용하면 오프라인 상태에서도 queryFn 을 실행할 경우 정상 데이터를 반환하기 때문이다.