Offline React Query

terry yoon·2023년 1월 21일
1

이 글은 다음 블로그 글을 일부 번역한 글입니다.

Issues in V3

리액트 쿼리는 오프라인 시나리오를 처리하기 위해 매우 잘 갖춰진(well-equipped) 도구이다. 리액트 쿼리는 캐싱 레이어를 제공하기 때문에, 캐시가 채워져 있는 동안, 네트워크 연결이 되어 있지 않아도 계속 작업을 할 수 있다. 버전 3에서 우리가 예상한데로 동작하지 않은 edge 케이스를 살펴보자.

1) 캐시에 데이터가 없는 경우

말했듯이 캐시가 채워져 있는 동안 버전 3에서는 잘 동작한다. 그러나 동작이 이상한 케이스는 다음과 같다.

  • 네트워크 연결이 되어 있고,링크를 탐색한다
  • 링크를 클릭할 때 네트워크 커넥션을 잃는다.

이 상황에서 벌어지는 일은 리액트 쿼리가 다시 연결될 때까지 계속 loading 상태로 남아있는 것이다. 브라우저 개발자 도구에서 실패한 네트워크 요청을 볼 수 있다. 이건 리액트 쿼리가 처음 요청을 보내고 실패했는데, 당신이 네트워크 연결이 없을 경우 다시 요청하지 않기 때문이다.

더 나아가, 리액트 쿼리 Devtools는 당신의 쿼리가 fetching 상태라고 보여주는데, 이건 항상 옳은건 아니다. 실제 쿼리는 pause 된 상태인데, 이 상태를 나타낼 컨셉이 없기 때문이다.

2) 재시도 없음

마찬가지로 위에서 만약 재요청 옵션을 꺼버린다면, 쿼리는 곧바로 error 상태로 갈 것이다. 왜 우리는 네트워크 연결이 없는 상태에서 쿼리 요청을 중단하기 위해서 retries 옵션이 왜 필요한걸까? (즉, paused 인 상태를 표현할 수 있는 방법이 없을까?)

 import { useQuery } from 'react-query'
 
 const result = useQuery(['todos', 1], fetchTodoListPage, {
   retry: false, 
 })

3) 네트워크가 필요 없는 쿼리 요청

(v3에서) 네트워크가 필요없는 쿼리는 어떤 이유로 요청이 실패할 경우, (네트워크가 필요없다면 그럴 필요가 없음에도) 다시 네트워크가 연결 될 때까지 멈춘다. 따라서 이런 쿼리들은 윈도우 포커스가 될 때 동작하지 않는다. 왜냐하면 이런 쿼리의 기능은 네트워크 연결이 없다면 완전히 불가능하기 때문이다.


요약하자면 여기에는 두 가지 주요 문제가 있다. 어떤 경우에 리액트 쿼리는 네트워크 연결이 필요 없는데도 필요 있다고 가정하고(네트워크가 필요없지만 필요할 때까지 기다리거나), 어떤 경우는 쿼리를 요청하면 안 되는 경우에 요청해버린다(현재 네트워크가 없는 상태인데 바로 쿼리를 요청해버림).

The New NetworkMode

v4에서는 이런 문제를 새로운 networkmode 세팅으로 제동을 걸고자 한다. 이 세팅으로, 우리는 online과 offline 쿼리를 명확히 구분할 수 있다. 이 옵션은 useQuery 뿐만 아니라 useMutation도 가능하며, 해당 옵션을 전역 또는 개별 쿼리별로 세팅할 수 있다는 의미이다.

network mode : online

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 상태가 될 수 있다는 의미이다. 예를 들어, 당신이 처음에 데이터를 성공적으로 불러왔지만 네트워크가 유실 될 경우 fetchStatuspaused가 된다.

혹은 loading이고 바로 paused 가 될 수도 있다 (mount 되어서 queryFn이 실행되는 중간에 네트워크가 유실 될 경우)

따라서 로딩 스피너를 보여주기 위한 조건으로 'loading'만 체크하는 건 불충분하다. 쿼리는 state:'loaindg'이지만, fetchStatus:'paused'일 수 있다.

fetchStatus

 import { useQuery } from 'react-query'
 
 const { fetchStatus } = useQuery(['todos', 1], fetchTodoListPage) 
 // status : fetching, paused, idle

우리는 쿼리가 동작하는 중임을 알리는 isFetching 플래그를 가지고 있었다. 비슷하게 paused 상태에서도, 쿼리는 successfetching이거나 errorfetching일 수 있다.

fetchingpaused 상태는 상호 배타적이기 때문에, 우리는 이들을 합쳐서 useQuery로부터 불러올 수 있는 새로운 fetchStatus를 만들었다.

  • fetching : 쿼리가 실제 동작하고 있다 - 요청이 진행 중이다.
  • paused : 쿼리가 동작하지 않는다 - 다시 연결될 때까지 중단된다.
  • idle : 쿼리가 현재 실행중이 아니다.

경험에 비추어, status는 data에 대한 정보를 제공한다(success는 데이터가 존재한다는 걸 의미하고, loading은 아직 데이터가 없다는 뜻이다)

다른 말로 말하면, fetchStatusqueryFn에 대한 정보를 제공한다(현재 동작하고 있는가 아닌가?)

isFetchingisPaused는 이 상태에서 파생된 것이다.

network mode : always

해당 모드에서는 리액트 쿼리가 네트워커 연결 여부를 신경쓰지 않는다. 항상 쿼리 함수가 실행되고 절대 paused 상태가 되지 않는다. 이 경우에서는 실제 네트워크 연결이 필요없는 환경에서는 리액트 쿼리를 실행하고 싶을 경우 사용할 수 있다.(e.g AsyncStorage를 읽거나, Promise.resolve(5)queryFn으로부터 반환받기를 원하거나)

  • 만약 queryFn이 실패할 경우, paused 가 아닌 error 상태가 바로 됨
  • refetchOnReconnection 옵션 설정 값이 false로 설정됨.

network mode : offlineFirst

이 모드는 v3의 리액트 쿼리가 동작하는 방식과 유사하다. 처음 queryFn은 (네트워크 연결 여부 상관 없이) 항상 실행 되고, 만약 실패할 경우 paused 상태가 된다. 이 모드의 경우는 리액트 쿼리 상위의 브라우저 캐시와 같은 추가적인 캐싱을 사용하는 경우 유용하다. 브라우저 캐시를 사용하면 오프라인 상태에서도 queryFn 을 실행할 경우 정상 데이터를 반환하기 때문이다.

profile
배운 것을 기록하는 FrontEnd Junior 입니다

0개의 댓글