【React】TanStack QueryとSuspenseを使って、data fetch, loading, errorの表示

2023年1月時点、TanStack Queryの最新バージョンはv4です。v3までは名前がReact Queryでしたがv4より変更となっておりますのでお間違いなく。

環境

  • react @18.2.0
  • react-query @4.20.4

TanStack Queryとは

まずは、TanStack Queryとは何かです。TanStack Queryは以下のような特徴があります。

  • 非同期のstate管理が行える
  • server stateの fetching, caching, synchronizing and updatingを簡単に行う事ができる
  • ReactのHooksであるuseEffectを使ったデータ取得方法は、clientのstateを扱うのに優れているが、非同期やserverのstateを扱うことができない
  • backgroundでデータの更新をしてくれ、データの更新が高速になる

TanStack Queryの使い方

TanStack Queryの使い方を理解する上で、まずは見慣れたuseEffect、useStateを使ったデータfetchの例を見てみましょう!その後にTanStack Queryと比較するとよりわかりやすくなると思います。

実行環境の用意

実行環境を以下の手順で用意します。

  1. プロジェクトを作成するための任意のディレクトリを作成
  2. プロジェクト立ち上げ
% yarn create react-app . --template typescript

yarnを使用して、typescriptで作成します。

3. dataをfetchするためにaxiosを使用するためinstallしておきます。

% yarn add axios

4. サンプルデータはJSONPlaceholderを使用します。

JSONPlaceholder - Free Fake REST API

以上で準備は完了です!

React Hooksの useEffect, useStateを使ったdata fetch

Hooks.tsxという名前でファイルを作り、以下のように記述します。

import axios from "axios";
import { useEffect, useState } from "react";

type Album = {
  userId: number;
  id: number;
  title: string;
};

export const Hooks = () => {
  const [albums, setAlbums] = useState<Album[]>([]);
  useEffect(() => {
    const fetchAlbums = async () => {
      const result = await axios.get<Album[]>(
        "https://jsonplaceholder.typicode.com/albums"
      );
      setAlbums(result.data);
    };
    fetchAlbums();
  }, []);

  return (
    <div>
      <p>Hooks</p>
      {albums?.map((album) => (
        <p key={album.id}>{album.title}</p>
      ))}
    </div>
  );
};
  • JSONPlaceholderは、何でも良いですが、ここではAlbumを使用しています。
  • albumsというstateを用意し、useEffectで初回レンダリング時にのみdata fetchされるようにし、setAlbumsで更新しています。

上記で作成したHooks componentをApp.tsxに追加して、表示の確認をします。

import "./App.css";
import { Hooks } from "./components/Hooks";

function App() {
  return (
    <div className="App">
      <Hooks />
    </div>
  );
}

export default App;

データを取得して表示できていることを確認できたかと思います。

Tanstack Queryを使ったdata fetch

Tanstack Queryを使うと、同じデータ取得をどう記述するか見ていきましょう!

まずはindex.tsxで、Appコンポーネントを囲むかたちで設定を記述します。

import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

const queryClient = new QueryClient();

const root = ReactDOM.createRoot(
  document.getElementById("root") as HTMLElement
);
root.render(
  <React.StrictMode>
    <QueryClientProvider client={queryClient}>
      <App />
    </QueryClientProvider>
  </React.StrictMode>
);

App.tsxではTanstackQueryコンポーネントを呼び出します。

import "./App.css";
import { TanstackQuery } from "./components/TanstackQuery";

function App() {
  return (
    <div className="App">
      <TanstackQuery />
    </div>
  );
}

コンポーネントはTanstackQueryという名前で作ります。

import { useQuery } from "@tanstack/react-query";
import axios from "axios";

type Album = {
  userId: number;
  id: number;
  title: string;
};

const fetchAlbums = async () => {
  const result = await axios.get<Album[]>(
    "https://jsonplaceholder.typicode.com/albums"
  );
  return result.data;
};

export const TanstackQuery = () => {
    const { isLoading, error, data } = useQuery<Album[]>({
    queryKey: ["album"],
    queryFn: fetchAlbums,
  });

  if (error) return <p>An error has occurred</p>;

  if (isLoading) return <p>Loading...</p>;

  return (
    <div>
      <p>Tanstack Query</p>
      {data?.map((album) => (
        <p key={album.id}>{album.title}</p>
      ))}
    </div>
  );
};

useQueryの第一引数には、Query固有のkeyを、第二引数には実行関数を記述します。

このkeyは任意に設定できるもので、何でもよいです。

useQueryは、loadingやerrorの状態も取得することができるため、isLoadingや、errorを使うことができます。

以上が一般的なTanStack Queryの使い方です。

Suspenseを使ったloading状態の表示

次に、Reactの標準ライブラリのSuspenseを使用して、loadingやerrorの状態を表示していきます。

– React
The library for web and native user interfaces

  • Suspenseの対象としたいコンポーネントをSuspenseで囲う
  • Suspenseにpropsとしてfallbackを渡し、loading中の処理を記述する
  • 上記より、useQueryのisLoadingは不要となる
  • useQueryの第三引数に、オプションとしてsuspenseを有効化する記述をする

import { Suspense } from "react";

function App() {
  return (
    <div className="App">
      <Suspense fallback={<p>Loading...</p>}>
        <TanStackQuery />
      </Suspense>
    </div>
  );
}

Suspenseで囲われたTanStackQueryコンポーネントの全体が、Suspenseの対象となります。

import { useQuery } from "@tanstack/react-query";
import axios from "axios";

type Album = {
  userId: number;
  id: number;
  title: string;
};

const fetchAlbums = async () => {
  const result = await axios.get<Album[]>(
    "https://jsonplaceholder.typicode.com/albums"
  );
  return result.data;
};

export const TanStackQuery = () => {
  const { isLoading, error, data } = useQuery<Album[]>({
    queryKey: ["album"],
    queryFn: fetchAlbums,
    suspense: true,
  });

  if (error) return <p>An error has occurred</p>;

  // suspenseを使用すると不要となる
  // if (isLoading) return <p>Loading...</p>;

  return (
    <div>
      <p>React Query</p>
      {data?.map((album) => (
        <p key={album.id}>{album.title}</p>
      ))}
    </div>
  );
};

TanStack Queryの一般的な使用法と同様に、loading中の表示をすることができました。

上記の方法だと、TanStackQueryコンポーネント内でのみ、suspense: trueの設定となります。

Suspenseのユースケースを考えると、プロジェクト内全体でsuspeseの設定をしたいので、この設定をindex.tsxにしていきます。

  • TanStackQuery内のsuspense: trueの設定は削除
  • index.tsxに設定を記述
Suspense | TanStack Query Docs
NOTE: Suspense mode for React Query is experimental, same as Suspense for data fetching itself. These APIs WILL change and should not be used in production unle...

import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      suspense: true,
    },
  },
});

const root = ReactDOM.createRoot(
  document.getElementById("root") as HTMLElement
);
root.render(
  <React.StrictMode>
    <QueryClientProvider client={queryClient}>
      <App />
    </QueryClientProvider>
  </React.StrictMode>
);

同様に、Suspenseが有効化されており、loadingが表示されることを確認できました。

Suspenseを使ったerrror状態の表示

上記ではSusupenseを使ったloadingの表示をすることができたので、次はerrorの表示をしていきます。

Suspenseでerrorのハンドリングを行う場合は、追加でreact-error-boundaryというライブラリが必要になるのでinstallします。

% yarn add react-error-boundary

  • SusupenseをErrorBoundaryで囲む
  • propsとしてfallbackを渡し、error時の処理を記述する
import { Suspense } from "react";
import { ErrorBoundary } from "react-error-boundary";
import "./App.css";
import { TanStackQuery } from "./components/TanStackQuery";

function App() {
  return (
    <div className="App">
      <ErrorBoundary fallback={<p>An error has occurred</p>}>
        <Suspense fallback={<p>Loading...</p>}>
          <TanStackQuery />
        </Suspense>
      </ErrorBoundary>
    </div>
  );
}

export default App;

error時にfallbackに記述した内容が表示されることを確認できました。

以上、お読みいただきありがとうございました。

コメント