React-query :: Query

기본 사용

const info = useQuery('unique_key', fetchData);
// fetchData는 promise 반환

unique한 key로 정의하며, key에 묶이는 데이터는 비동기 데이터이다.

useQuery가 반환하는 result는 비동기 데이터의 상태를 담고 있다.

  • 상태
    • loading
      • isLoading이 true가 된다.
    • error
      • isError가 true가 되며, error에 에러 정보가 담긴다.
    • success
      • isSuccess가 true가 되며, data에 정보가 담긴다.
    • idle

 

사용 예제

const { isLoading, isError, data, error } = useQuery('key', fetchData);
// const { status, data, error } = useQuery('key', fetchData);

if(isLoading){ // if(status === 'loading')
    return <span>Loading..</span>
}

if(isError){
    return <span>Error : {error.message}</span>
}

return (
    <div>{data}</div>
);

 

 

Query Keys

할당해준 key는 cache, refatch, share에 사용된다.

key는 기본적으로 string 형태이지만 array나 object 형태로 가능하다.

string으로 key가 넘겨지면 내부적으로는 해당 string을 포함하는 array로 key가 변환된다.

array나 object는 내부적으로도 그 자체로 key가 된다.

key에 해당하는 array의 내부에 object가 있다면, 해당 object의 내부 요소는 상관없이 같은 object로 취급된다.

  • 오른쪽의 셋 다 같은 key
useQuery(['todos', { status, page }], ...)
useQuery(['todos', { page, status }], ...)
useQuery(['todos', { page, status, other: undefined }], ...)

해당 변수가 바뀌면 fetch가 진행되어야 할 때, 해당 변수를 key로 등록하는 것이 좋다.

 

Query Functions

useQuery에 등록되는 함수는 data를 resolve하고 error를 throw하는 promise를 return 해야 한다.

throw된 error는 error 변수에 저장된다.

axios와 graphql-request는 자동으로 error를 throw하지만, fetch는 그렇지 않으므로 직접 throw하는 함수를 작성해주어야 한다.

query function의 parameter로 설정된 key가 넘겨진다.

 

Parallel Queries

여러 useQuery를 여러 번 실행하면 query들이 별도의 설정 없이 병렬 실행된다.

render시에 실행되는 query의 개수를 조절해야하는 경우라면, hook의 규칙을 깰 수도 있기 때문에 그냥 쓸 수 없다. 대신에 useQueries hook을 이용해서 동적으로 병렬 실행되는 query의 개수를 제어할 수 있다.

function App({ users }) {
   const userQueries = useQueries(
     users.map(user => {
       return {
         queryKey: ['user', user.id],
         queryFn: () => fetchUserById(user.id),
       }
     })
   )
 }

useQueries는 query option object의 array를 받아서 result의 array를 반환한다.

 

Dependent Queries

이전 query가 끝난 후에 실행되는 query이다. useQuery의 enabled 옵션에 있어야 하는 data를 넘겨주면 된다.

useQuery(
   ['projects', userId],
   getProjectsByUser,
   {
     // The query will not execute until the userId exists
     enabled: !!userId,
   }
 )
  • userId가 있어야 실행된다.

enabled 옵션에 false를 넘겨주면 자동으로 query가 실행되지 않는다.

  • cached data가 있을 떄는 success를 return 하며 cache된 데이터를 return 한다.
  • cached data가 없을 때는 idle 상태가 된다.
  • 이제 mount와 동시에 fetch 되지 않는다.

 

Background Fetching Indicators

query가 refetching되는 것에 대한 상태를 파악하고 싶을 때는 isFetching을 사용하면 된다.

const { status, data: todos, error, isFetching } = useQuery(
     'todos',
     fetchTodos
   )

모든 query가 하나라도 fetch되고 있는 것을 파악하고 싶다면 global 변수로 useIsFetching을 사용하면 된다.

import { useIsFetching } from 'react-query';
const isFetching = useIsFetching()

 

 

Query Retries

useQuery가 실패하면 react-query는 자동으로 재시도를 한다. default로 3번 재시도를 하는데, 재시도 횟수는 retry 옵션을 이용하여 재설정할 수 있다.

useQuery(['todos', 1], fetchTodoListPage, {
   retry: 10, // Will retry failed requests 10 times before displaying an error
 })

마찬가지로 retryDelay 옵션을 줘서 delay 시간을 조정할 수도 있다.

const result = useQuery('todos', fetchTodoList, {
   retryDelay: 1000, // Will always wait 1000ms to retry, regardless of how many retries
 })

 

Pagination

페이지네이션 구현은 쉽다.

const result = useQuery(['projects', page], fetchProjects)

하지만 이렇게 코드를 짜면 매번 새로운 query가 실행되기 때문에 success와 loading이 계속 반복되는 것을 확인할 수 있다.

keepPreviousData를 사용하면 다른 query라도 상태를 반복하지 않고 실행 가능하다. keepPreviousData를 true로 설정하면 된다.

  • 다음 data를 받아오는 동안 이전의 데이터가 유지된다.
  • data가 도착하면 이전의 data를 대체한다.
  • isPreviousData로 이전 데이터인지 확인할 수 있다.
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 })

   return (
     <div>
       {isLoading ? ( // 로딩중
         <div>Loading...</div>
       ) : isError ? ( // 에러
         <div>Error: {error.message}</div>
       ) : (
         <div>
           {data.projects.map(project => (
             <p key={project.id}>{project.name}</p>
           ))}
         </div>
       )}
       <span>Current Page: {page + 1}</span>
       <button
         onClick={() => setPage(old => Math.max(old - 1, 0))}
         disabled={page === 0}
       >
         Previous Page
       </button>{' '}
       <button
         onClick={() => {
           if (!isPreviousData && data.hasMore) { // 데이터가 더 있고, 데이터가 불러와진 상태이면
             setPage(old => old + 1)
           }
         }}
         // Disable the Next Page button until we know a next page is available
         disabled={isPreviousData || !data?.hasMore}
       >
         Next Page
       </button>
       {isFetching ? <span> Loading...</span> : null}{' '} // 다시 받아오는 중
     </div>
   )
  • useInfiniteQuery hook과 함께 사용할 수도 있다.
반응형