toshi-toma blog

主にフロントエンド、作業ログあとは色々なメモ ✍️ 🍅

React Queryを使った開発

最近、React QueryやSWRといった、キャッシュ機構をもったHooksを使う開発が多くなっています。

自分も、ReactのプロジェクトでReact Queryを採用しました。その過程で迷ったことなどのメモです。

react-query.tanstack.com

React Query vs SWR

React QueryとSWRは似たライブラリです。 自分もどちらを採用しようか迷ったタイミングで、調べてみました。

参考

zenn.dev

scrapbox.io

ただ、調べてみても、できることは両者大きな違いが無さそうでした。

最終的には、React QueryにはDevToolsがあり、SWRには無い。という点でReact Queryを選択しました。 DevToolsでは、どんなキーでどんなデータがキャッシュされているのか確認することができます。

github.com

React Queryについて

React Queryの何が良いのかについて触れておきます。

Reactでの開発によくある実装として、以下のようなものがあります

  1. データを保持するstateを定義する
  2. useEffectでAPIデータ呼び出しを行う
  3. API呼び出し中はローディングを表示する
  4. API呼び出しが終わったら、ローディングを終了して、結果をstateに更新する
  5. 更新されたstateをもとにコンポーネントを表示する

コードで表すと、以下のようになります。

const App = () => {
  const [data, setData] = useState(null);
  useEffect(() => {
    fetchData().then((res) => setData(res));
  }, []);

  if (!data) {
    return <Loading />;
  }

  return <MyComponent data={data} />;
};

もし、Reduxを使っていた場合は、Storeに取得したデータを格納します。

ここで、APIのレスポンスを格納するためのStateやStoreを用意するのではなく、APIのレスポンスをキャッシュすればコンポーネントがStateを定義しなくて良いよね。というアプローチをReact Queryが提供してくれます。

React Queryを使った場合は以下のようになります。

const App = () => {
  const { data } = useQuery("fetch-data-key", fetchData);

  if (!data) {
    return <Loading />;
  }

  return <MyComponent data={data} />;
};

useQueryというHooksにキャッシュのキーとデータフェッチ用の関数を渡します。そうすると、fetchDataの結果をキャッシュします。 すると、Appコンポーネントが再レンダリングされる際はキャッシュされたデータをHooksが返します。

もしキャッシュが無いと、Appコンポーネントの再レンダリングごとにAPI呼び出しが発生しそうに見えますが、キャッシュのおかげでそのような問題もありません。

APIからデータを取得して、それを表示するのがメインのアプリケーションであれば、Reduxなどは使わずにReact Queryだけで十分に感じました。

自分の場合は、管理画面を開発したので、このユースケースにとてもマッチしており、シンプルなコードで開発を進めることができました。

React Queryの導入

ここからは React Queryの導入について説明します。

まずはパッケージをインストールします。

$ npm i react-query

次は、QueryClientProviderを設定します。また、DevToolsを利用する場合はReactQueryDevtoolsを配置します。 QueryClientProviderに渡しているQueryClientオブジェクトには、細かい設定を行えます。 例えば、どれくらいの期間はキャッシュから返すのか?など。

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

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 1 * 60 * 1000, // 1分間はキャッシュから返す
      refetchOnWindowFocus: false, // ウィンドウにフォーカスしたタイミングでデータを再取得しないように
    },
  },
});

const App = () => {
  return (
    <QueryClientProvider client={queryClient}>
      <ReactQueryDevtools initialIsOpen={false} />
      <MyComponent />
    </QueryClientProvider>
  );
};

あとは、各コンポーネントuseQueryといったHooksを利用するだけです。

useQuery

最後に、主に利用しているuseQueryについて説明します。他にもいくつかHooksが用意されていますが、自分は使わなかったので触れません。

useQueryの第1引数には、キャッシュのキーを指定します。このキーが同じ場合は、キャッシュからデータが返されるので、予期せず違うAPI呼び出しなのに同じキーを指定しないようにしましょう。

第2引数には、Promiseを返す関数を指定します。自分はaxiosと組み合わせて利用しました。

Query Cancellation | React Query | TanStack

const App = () => {
  const { data } = useQuery("fetch-data-key", fetchData);

  if (!data) {
    return <Loading />;
  }

  return <MyComponent data={data} />;
};

第1引数には配列を指定することも可能なので、ページネーションがあるような場合に便利です。

const { data } = useQuery(["fetch-data-key", pageIndex], () =>
    fetchData(pageIndex)
);

任意のタイミングでデータを再取得したい場合のために、refetch関数も用意されています。

const App = () => {
  const { data, refetch } = useQuery("fetch-data-key", fetchData);

  if (!data) {
    return <Loading />;
  }

  return <MyComponent data={data} refetchData={refetch} />;
};

staleTime

デフォルトでは、staleTimeが0になっているので、キャッシュを使わずに毎回APIからデータを取得します。 必要に応じて、どれくらいの時間キャッシュから返すのかを指定することになります。

Initial Query Data | React Query | TanStack