React Query 사용해보기

HYl·2022년 4월 10일
6

React Query

목록 보기
3/4

이전의 글에서 우아한테크세미나 강의를 듣고 React Query에 대하여 정리해보는 글을 작성하였다. 복습도 해볼겸, 현재 진행 중인 프로젝트에 react query를 적용시켜, server state 를 관리 해보고자 한다.

현재 Next.js + TypeScript 를 사용하여 진행중인 프로젝트에 서버의 데이터를 가져오기 위하여 React Query 를 적용해 볼 것이다.


QueryClientProvider, devtools

먼저 npm i react-query 를 통해 설치를 해준다.
최상단인 파일에 QueryClientProvider 를 필수적으로 해주어야 사용할 수 있다.

참고로, react-query에서 devtools 를 지원한다. devtools는 react-query/devtools 패키지에 포함되어 있어서 추가로 설치할 필요가 없다.

또한, process.env.NODE === 'production' 일 때 프로덕션 번들에 포함되지 않아, 프로덕션 빌드 중에 devtools를 제외시키는 것을 걱정할 필요가 없다.

devtools를 통하여 디버깅을 유연하게 할 수 있다. devtools는<ReactQueryDevtools /> 를 코드에 작성하면 확인할 수 있다.

import { QueryClient, QueryClientProvider } from 'react-query'
import { ReactQueryDevtools } from 'react-query/devtools'

import type { AppProps } from 'next/app'
import Layout from '../components/Layout'

export default function App({ Component, pageProps }: AppProps) {
  const client = new QueryClient()
  return (
    <Layout>
      <QueryClientProvider client={client}>
        <Component {...pageProps} />
        <ReactQueryDevtools />
      </QueryClientProvider>
    </Layout>
  )
}

Home.tsx

The Movie Database API 를 이용하여 api를 호출하였다.

data를 불러올 때는, Promise API를 활용하는 HTTP 비동기 통신 라이브러리인 axios를 사용하였다.

// api.ts

import axios from 'axios'

const api = axios.create({
  baseURL: 'https://api.themoviedb.org/3/',
})

export const getMovies = () =>
  api.get(`/movie/popular?api_key=${API_KEY}`).then((res) => res.data)

export const updateMovies = (id: number) =>
  api
    .get(`/movie/popular?api_key=${API_KEY}&page=${id}`)
    .then((res) => res.data)
// Home.tsx

import { useQuery } from 'react-query'
import { getMovies } from '../utils/api'

interface IGetMoviesProps {
  results: IMovieProps[]
}

interface IMovieProps {
  id: number
  original_title: string
}

export default function Home() {
  const { data, isLoading, isError } = useQuery<IGetMoviesProps>(
    'movies',
    getMovies
  )

  if (isLoading) {
    return <h4>Loading</h4>
  }

  if (isError) {
    return <h4>Something went wrong !!</h4>
  }

  return (
    <>
      {data?.results.map((movie) => (
        <div key={movie.id}>
          <h4>{movie.original_title}</h4>
        </div>
      ))}
    </>
  )
}

'movies' 와 같이 query key 를 사용하는 이유는 caching을 사용하기 위해서이다.
caching은 데이터가 한 번 fetch가 되면 다시 fetch를 하지 않겠다는 것을 의미한다.

예를 들어, About 페이지로 넘어갈 때, About 컴포넌트에서도 동일하게 'movies' 동일한 쿼리를 사용했다면 react query는 fetch를 또 하지 않을 것이다. 왜냐하면 'movies' 라는 key를 가진 쿼리가 이미 cache에 있기 때문이다.


About.tsx

import { useQuery } from 'react-query'
import { updateMovies } from '../utils/api'

export default function About() {
  const { data, isLoading } = useQuery<IGetMoviesProps>('about', () =>
    updateMovies(Math.floor(Math.random() * 10))
  )

  if (isLoading) {
    return <h4>Loading</h4>
  }

  return (
    <>
      {data?.results.map(({ id, original_title }) => {
        return (
          <div key={id}>
            <h4>{original_title}</h4>
          </div>
        )
      })}
    </>
  )
}

updateMovies 함수를 호출하여 데이터를 가져올 때, page 단위로 가져오게끔 설정을 해놓았다.
Math.floor(Math.random() * 10)) , 즉 0 ~ 10 사이의 숫자를 랜덤하게 가져와 page의 데이터를 나타나게 하였는 데, 만약 숫자가 0이 나올 경우에 page=0 일때의 데이터가 없기 때문에 error가 나올 것이다.

그러나, query 에서는 기본적으로 retry의 default 값이 3 이기 때문에, 에러가 발생하여도 3번의 api 호출을 한다. 따라서 다음의 랜덤 값이 0이 아닐 경우 정상적으로 data를 불러올 수 있다.

개발자 도구를 열어서 , Network 탭을 확인해보면
page=0 일 때는 data가 없기 때문에 실패가 된 것을 확인할 수 있고, 따로 한번 더 요청을 하지 않아도 자동적으로 api가 요청이 되어 page=7 일때의 data가 성공적으로 받아와지는 것을 확인 해볼 수 있다.


만약, 여러 개의 api 를 동시에 호출 한다면?

About.tsx

예를 들어, fetcher1, fetcher2, fetcher3 3개의 axios 함수를 불러온다고 가정을 해보자. 그렇다면 dataisLoading의 변수명이 동일하게 3개가 발생하게 되어 에러가 발생한다.
이런 상황에서는 변수명을 각각 custom 하여 사용할 수 있다.

import { useQuery } from 'react-query'

export default function About() {
  // 3개의 api를 동시에 받는 상황
  const { data: oneData, isLoading: oneLoading } = useQuery('about1', fetcher1)
  const { data: twoData, isLoading: twoLoading } = useQuery('about2', fetcher2)
  const { data: threeData, isLoading: threeLoading } = useQuery('about3', fetcher3)
  
  // oneLoading이 로딩중이거나, twoLoading이 로딩중이거나, threeLoading이 로딩중이거나
  const loading = oneLoading || twoLoading || threeLoading

  if (loading) {
    return <h4>Loading</h4>
  }

  return (
    <>
      {oneData?.results.map(({ id, original_title }) => {
        return (
          <div key={id}>
            <h4>{original_title}</h4>
          </div>
        )
      })}
    </>
  )
}

refetch

해당 query를 refetch하는 함수를 사용해보자.

우선 options에 enabled: false 으로 설정해두어 component가 mount 하는 순간 query가 작동하지 않게 설정을 해둘 것이다.

button 을 클릭하면 refetch 함수가 작동하여 해당 query를 다시 작동하게 해준다.

import { useQuery } from 'react-query'
import { getMovies } from '../utils/api'

export default function Home() {
  const { data, isLoading, isError, refetch } = useQuery<IGetMoviesProps>(
    'home',
    getMovies,
    {
      enabled: false,
    }
  )
  if (isLoading) {
    return <h4>Loading</h4>
  }

  if (isError) {
    return <h4>Something went wrong !!</h4>
  }

  const handleRefetch = () => {
    refetch()
  }

  return (
    <>
      <button onClick={handleRefetch}>refetching !</button>

      {data?.results.map((movie) => (
        <div key={movie.id}>
          <h4>{movie.original_title}</h4>
        </div>
      ))}
    </>
  )
}

버튼 클릭 시, refetch가 되어 data가 정상적으로 불러와지는 것을 확인할 수 있다.

그렇다면 여러 쿼리들을 동시에 refetch 하고 싶다면 어떻게 할 수 있을까?
코드를 효율적으로 작성하기 위하여, QueryClient 를 이용하여 cache에 접근해보자.


QueryClient

  • The QueryClient can be used to interact with a cache
  • 모든 쿼리들을 통제할 수 있기 때문에, query를 삭제할 수도, 취소할 수도 refetch 할 수도 있다.

위에서 사용하였던 예제를 다시 사용해보자,

import { useQuery, useQueryClient } from 'react-query'

export default function About() {
  const queryClient = useQueryClient();
  
  // query key를 array 형태로 사용하였다.
  const { data: oneData, isLoading: oneLoading } = useQuery(['about', 'about1'], fetcher1)
  const { data: twoData, isLoading: twoLoading } = useQuery(['about', 'about2'], fetcher2)
  const { data: threeData, isLoading: threeLoading } = useQuery(['about', 'about3'], fetcher3)
  
  // about key를 가진 쿼리들을 refetch 한다.
  const onRefresh = async () => {
    queryClient.refetchQueries(['about'])
  }
  
  return (
    <>
      ...
    </>
  )
}
profile
꾸준히 새로운 것을 알아가는 것을 좋아합니다.

0개의 댓글