본문 바로가기

TIL

React Query

정리를 하게 된 계기는 Query를 사용하고 있었고, 사용했었고 어떻게 흘러가는지 알고 있다고 생각했다. 하지만 설명을 하라고 하면 정확하게 내가 알고 있는대로 설명할 수 없는 나를 보고 이건 모르는것이다. 라고 생각하게 되었고, 정리를 통해서 설명해보려고 한다.

 

React-Query 이점

우리가 React-Query를 사용하는 이유는 데이터 조회, 캐싱, 동기화, 업데이트 등 많은 이점이 있다는 것이다.

Query Refetching 조건

1. 런타임에 stale인 특정 쿼리 인스턴스가 다시 만들어졌을 때

2. window가 다시 포커스가 되었을때(옵션으로 조절가능)

3. 네트워크가 다시 연결되었을때(옵션으로 조절가능)

4. refetch interval이 있을때 요청 실패한 쿼리는 디폴트로 3번 더 백그라운드단에서 요청하며, retry, retryDelay 역시 옵션으로 간격과 횟수를 커스텀 가능

 

Query API

Dependent Query : 쿼리가 동기적으로 실행해야 하는 경우 , useQuery의 옵션 중 enabled를 사용

function App(){
  const { data: user } = useQuery(['user', email], getUserByEmail);
  
  const userId = user?.id
  
  const { isIdle, data: projects } = useQuery(
    ['projects', userId],
    getProjectsByUser,
    {
      // 해당 query는 userId가 존재할때까지 실행되지 않는다.
      enabled: !!userId,
    }
  )
}

 

enabled option을 통해 특정 data가 존재하면 Query가 시작하도록 설정 가능. (그 전까지 해당 query의 isIdle 값은 true)

 

Background fetching indicator : 특정 query가 background에서 fetching될 때의 값은 isFetching을 통해 확인

function Todos() {
   const { status, data: todos, error, isFetching } = useQuery(
     'todos',
     fetchTodos
   )
 
   return status === 'loading' ? (
     <span>Loading...</span>
   ) : status === 'error' ? (
     <span>Error: {error.message}</span>
   ) : (
     <>
       {isFetching ? <div>Refreshing...</div> : null}
 
       <div>
         {todos.map(todo => (
           <Todo todo={todo} />
         ))}
       </div>
     </>
   )
 }

 

** 모든 Query에 대한 refetching을 체크하고 싶을때는, useIsFetching hook을 사용

import { useIsFetching } from 'react-query'

function GlobalLoadingIndicator(){
  const isFetching = useIsFetching();
  
  return isFetching ? (
    <div> Queries are fetching in the background...</div>
  ) : null
}

 

Query Retries : 아무 옵션도 주지 않았을 시 React Query는 기본적으로 3회 retry를 진행. (option 값을 변경함으로써 수정 가능)

import { useQuery } from 'react-query'
 
 const result = useQuery(['todos', 1], fetchTodoListPage, {
   retry: 10, // error를 띄우기 전 10번 retry 한다.
	 retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000),
 })

 

Paginated / Lagged Queries : 전에 Query된 데이터가 존재할때, 기존 데이터를 우선적으로 보여주고 background에서 데이터를 갱신하고 싶다면 옵션중에 keepPreviousData 이용

   const [page, setPage] = React.useState(0)
 
   const fetchProjects = (page = 0) => fetch('/api/projects?page=' + page).then((res) => res.json())
 
   const {
     isLoading,
     isError,
     error,
     data,
     isFetching,
     isPreviousData,
   } = useQuery(['projects', page], () => fetchProjects(page), { keepPreviousData : true })

이 옵션은 페이지네이션을 구현할 때 유용하게 사용할 수 있다. 캐시되지 않은 페이지를 가져올 때 화면에서 목록이 사라지는 깜빡임 현상을 방지할 수 있다.

 

useMutation : useMutation hook의 경우 server side query에 사용한다. 즉, 데이터 생성/갱신/삭제에 해당하는 query에 사용한다.

function App() {
   const mutation = useMutation(newTodo => {
     return axios.post('/todos', newTodo)
   })
 
   return (
     <div>
       {mutation.isLoading ? (
         'Adding todo...'
       ) : (
         <>
           {mutation.isError ? (
             <div>An error occurred: {mutation.error.message}</div>
           ) : null}
 
           {mutation.isSuccess ? <div>Todo added!</div> : null}
 
           <button
             onClick={() => {
               mutation.mutate({ id: new Date(), title: 'Do Laundry' })
             }}
           >
             Create Todo
           </button>
         </>
       )}
     </div>
   )
 }

 

주로 mutate로 데이터 추가/삭제/갱신 작업을 진행 후 invalidQuries로 기존 조회에 사용했던 query를 무효화함으로써 자동 데이터 갱신을 진행할 수 있다.

 import { useMutation, useQueryClient } from 'react-query'
 
 const queryClient = useQueryClient()
 
 // mutation 성공 시 onSuccess callback 실행됨
 const mutation = useMutation(addTodo, {
   onSuccess: () => {
     queryClient.invalidateQueries('todos')
     queryClient.invalidateQueries('reminders')
   },
 })

 

마무리

몰랐었던 리액트 쿼리의 옵션들에 대해서도 알게되었고, 그 옵션의 역할에 대해서도 알게 되었다. 실전에서 조금 더 사용해 볼 수 있는 옵션들을 많이 알게 되었다!

 

 

참고 할 사이트

https://velog.io/@seeh_h/React-query#query-retries

https://tkdodo.eu/blog/practical-react-query

https://velog.io/@kimmiri235/React-Query%EB%A5%BC-%EB%B0%B0%EC%9B%8C%EB%B3%B4%EC%9E%90