React Queryを使用して表示


以前の記事では、優雅な技術セミナー講座を聞き、ReactQueryについてのまとめ記事を書いていました.復習のために、進行中のプロジェクトにreact queryを適用して、サーバのステータスを管理します.
現在のNext.React Queryを適用して、js+TypeScriptを使用したプロジェクトにサーバ上のデータをインポートします.

QueryClientProvider, devtools


まずnpm i react-queryによって取り付けます.
使用するには、最上位ファイルにQueryClientProviderを指定する必要があります.
react-queryはdevtoolsをサポートしていることに注意してください.devtoolsはreact-query/devtoolsパッケージに含まれており、追加のインストールは必要ありません.
また、プロセス.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を呼び出します.
データのロードには、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を使用するのは、キャッシュを使用するためです.
キャッシュは、データがいったん取り戻されると、二度と取り戻されないことを意味します.
たとえば、Aboutページに移動するときに、Aboutコンポーネントも同じ'movies'クエリを使用する場合、react queryはfetchを実行しません.'movies'というキーワードクエリが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関数を呼び出してデータをインポートする場合は、ページ単位でインポートするように設定します.
Math.floor(Math.random(*)*10)は、ページ上のデータを表示するために0から10の間の数字をランダムに入力し、0の場合page=0のデータがないためエラーが発生します.
ただし、queryは基本的にretry의 default 값이 3なので、エラーが発生してもapiは3回呼び出されます.したがって、次のランダム値が0でない場合、データを正常にロードできます.개발자 도구を開き、Networkのラベルページを表示します.page=0では、データがないので、실패と確認でき、もう1度要求する必要はなく、apiが要求されたことを自動的に確認でき、page=7でのデータは성공で受信される.

複数のapiを同時に呼び出すとしたら?


About.tsx


たとえば、fetcher 1、fetcher 2、fetcherの3つのaxios関数が読み込まれているとします.そうであれば、dataisLoadingの変数名に3つのエラーが同時に発生します.
この場合、変数名を個別にカスタマイズできます.
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を再構築する関数を使用します.
まずoptionsをenabled: falseに設定し、componentをmountに設定した瞬間にqueryを작동하지 않게に設定します.
ボタンをクリックすると、refetch関数がクエリーを開始します.
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>
      ))}
    </>
  )
}

ボタンをクリックすると、refectを行い、データが正常にロードされていることを確認できます.
では、複数のクエリーを同時に再構築するにはどうすればいいのでしょうか.
コードを効率的に記述するために、QueryClientを使用してcacheにアクセスします.

QueryClient

  • The QueryClient can be used to interact with a cache
  • のすべてのクエリーを制御できるため、クエリーを削除したり、クエリーをキャンセルしたり、クエリーを再構築したりすることができます.
  • 上記の例を再使用しましょう.
    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 (
        <>
          ...
        </>
      )
    }