1
/
5

Reactでローディング、エラー画面をササッと作る

はじめに

Webアプリをつくるにあたって、メインではないけれども重要な要素があります。
それが以下の2つです。

  • ローディング画面
    →データ取得などに時間がかかるときに表示される。
  • エラー画面
    →何らかの意図しない操作などがあったときに表示される。

今回はこれらの画面をササッと作る方法を確認します。

「ササッと」とはいえ、今回も全ソースコードを載せているので全体の文量は長めです。

今回のゴール

プルダウンで選択したデータを読み込むときにローディング画面を表示させます。

不正なデータを選択された場合にはエラー画面を表示させます。

事前準備

実行環境

本記事においては以下を使用しています。
React:18.2.0
TypeScript:5.2.2
Vite:5.2.2
Node.js:18.20.2

ライブラリのインストール

React QueryとReact Error Boundaryのインストールを行います。

npm install @tanstack/react-query react-error-boundary

※本記事作成時点でのそれぞれのバージョンは以下の通りです。
@tanstack/react-query:5.51.23
react-error-boundary:4.0.13

ライブラリの概要

React Queryは非同期の状態管理ができるライブラリです。データフェッチをしたときのローディング状態の管理などに向いています。後述のSuspenseとの相性が非常に良いです。

詳細については以下のQiitaの記事が非常に参考になります。

React Queryはデータフェッチライブラリではない。非同期の状態管理ライブラリだ。 - Qiita
はじめにこの記事はDominikさんが執筆された「Thinking in React Query」を参考にReact Queryの考え方をまとめたものになります。DominikさんはTanStac...
https://qiita.com/taisei-13046/items/05cac3a2b4daeced64aa

なお、便宜上React Queryと記載していますが、正確にはTanStack Queryと名前が変わっています。

React Error Boundaryは画面描画中のエラーを拾って、壊れたコンポーネントの代わりにエラー表示用のコンポーネントを表示させるためのライブラリです。

詳細については以下の公式サイトの説明が参考になります。
(ライブラリ自体ではなく、エラーバウンダリの概念についての説明になります。)

Component - React
The library for web and native user interfaces
https://ja.react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary

メインコンポーネントの作成

メインとなるコンポーネントのソースコードは以下の通りです。

/* App.tsx */
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import DataDisplay from "./DataDisplay";
import { useState } from "react";

const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: 1,
},
},
});

const App = () => {
const [id, setId] = useState("1");
return (
<QueryClientProvider client={queryClient}>
<div>
<h1>デモアプリ</h1>
<select value={id} onChange={(e) => setId(e.target.value)}>
<option value="1">アイテム1</option>
<option value="2">アイテム2</option>
<option value="3">アイテム3</option>
<option value="4">無効なアイテム</option>
</select>
<DataDisplay id={id} />
</div>
</QueryClientProvider>
);
};

export default App;

ポイントは1つです。

QueryClient、QueryClientProviderの設定

React Queryでデータを取得するuseQuery系の処理を使用する場合には、事前の設定が必要となります。

QueryClientでインスタンスを生成し、生成したインスタンスをQueryClientProviderに設定します。

これにより、QueryClientProviderコンポーネントで囲んだコンポーネント内でuseQuery系のデータフェッチ処理が使用できるようになります。

データ表示用コンポーネントの作成

データを表示するコンポーネントのソースコードは以下の通りです。

/* DataDisplay.tsx */
import { Suspense } from "react";
import { ErrorBoundary } from "react-error-boundary";
import Loading from "./Loading";
import ErrorFallback from "./ErrorFallback";
import DataContent from "./DataContent";
import { useQueryErrorResetBoundary } from "@tanstack/react-query";

const DataDisplay = ({ id }: { id: string }) => {
const { reset } = useQueryErrorResetBoundary();
return (
<ErrorBoundary
FallbackComponent={ErrorFallback}
onReset={() => {
reset();
}}
>
<Suspense fallback={<Loading />}>
<DataContent id={id} />
</Suspense>
</ErrorBoundary>
);
};

export default DataDisplay;

ポイントは2つです。

ErrorBoundaryコンポーネントの使用

Reactのコンポーネントでエラーが発生した場合、そのエラーについてのログが画面に出力されるなどして、画面が正しく表示されなくなります。

ErrorBoundaryコンポーネントを使うことで、このコンポーネント内でエラーが発生した場合、FallbackComponentに指定したコンポーネントが代わりに表示されます。

また、エラー状態から復帰するための処理をonResetに指定することができます。
指定した処理は、FallbackComponentに指定したコンポーネント内でpropsとして受け取ることができます。

今回はuseQueryErrorResetBoundaryのresetを設定しました。
直前のクエリのエラー状態をリセットし、クエリを再実行することができる処理となっています。

Suspenseコンポーネントの使用

SuspenseはもともとReactに用意されているコンポーネントです。このコンポーネント内の読み込みが完了するまでは、fallbackに指定したコンポーネントが表示されます。

ローディング画面の実装自体は、このSuspenseを使うことで簡単に実装できます。

エラー表示用コンポーネントの作成

エラー表示のコンポーネントのソースコードは以下の通りです。

/* ErrorFallback.tsx */
import { FallbackProps } from "react-error-boundary";

const ErrorFallback = ({ error, resetErrorBoundary }: FallbackProps) => {
return (
<div role="alert">
<p>エラーが発生しました:</p>
<pre>{error.message}</pre>
<button onClick={resetErrorBoundary}>再試行</button>
</div>
);
};

export default ErrorFallback;

ポイントは2つです。

エラー情報の使用

FallbackPropsはReact Error Boundaryで用意している型定義です。

errorにはエラー発生時のエラー情報のオブジェクトが自動で設定されます。
プロパティとしてはmessageのほか、スタックトレースを取得できるstack、エラー名を取得できるnameがあります。

エラーリセット処理の使用

resetErrorBoundaryには、ErrorBoundaryコンポーネントのonResetに設定した処理が渡されます。

今回は再試行ボタンを押すことでエラーリセット処理を実行するようにしました。

ローディング用コンポーネントの作成

ローディング用コンポーネントのソースコードは以下の通りです。

/* Loading.tsx */
const Loading = () => {
return <div>ロード中...</div>;
};

export default Loading;

データ用コンポーネントの作成

データ用コンポーネントのソースコードは以下の通りです。
useDataの中身は後ほど確認します。

/* DataContent.tsx */
import { useData } from "./functions";

const DataContent = ({ id }: { id: string }) => {
const { data } = useData(id);

return (
<div>
<h2>{data.title}</h2>
<p>{data.description}</p>
</div>
);
};

export default DataContent;

データ取得処理の作成

データ取得処理のコードは以下の通りとなります。

/* functions.ts */
import { useSuspenseQuery } from "@tanstack/react-query";

interface DataItem {
id: string;
title: string;
description: string;
}

const mockData: Record<string, DataItem> = {
"1": {
id: "1",
title: "最初のアイテム",
description: "これは最初のアイテムです",
},
"2": {
id: "2",
title: "2番目のアイテム",
description: "これは2番目のアイテムです",
},
"3": {
id: "3",
title: "3番目のアイテム",
description: "これは3番目のアイテムです",
},
};

export const fetchData = async (id: string): Promise<DataItem> => {
// 擬似的な遅延を追加
await new Promise((resolve) => setTimeout(resolve, 1000));

if (id in mockData) {
return mockData[id];
}
throw new Error("データが見つかりません");
};

export const useData = (id: string) => {
return useSuspenseQuery({
queryKey: ["data", id],
queryFn: () => fetchData(id),
});
};

ポイントは1つです。

useSuspenseQueryの使用

データ取得をするための処理です。引数としてオブジェクトを渡すことで、様々な設定をすることができます。

queryKeyはクエリを一意に特定するためのキーです。

queryFnにはデータをフェッチする処理を指定します。

必須のプロパティはこの2つで、他にも様々なプロパティがあります。

useSuspenseQueryはReactのSuspenseといっしょに使うことを想定した作りになっており、ここで指定した処理が完了するまでは、ローディング状態として扱われます。

まとめ

ローディング、およびエラー画面はメインの処理ではないのでおろそかになりがちですが、ユーザの使いやすさを考えると、それぞれしっかり作り込んでおくことが必要です。

今回紹介した2つのライブラリを使うことで、エラーやデータの取得状態を特別意識することなく、簡単に処理を作成することができました。

Reactには便利なライブラリが他にもたくさんあるので、うまく使ってより実践的なWebアプリを作っていきたいですね。




株式会社JOINT CREWからお誘い
この話題に共感したら、メンバーと話してみませんか?
株式会社JOINT CREWでは一緒に働く仲間を募集しています
3 いいね!
3 いいね!

同じタグの記事

今週のランキング

吉田 光輔さんにいいねを伝えよう
吉田 光輔さんや会社があなたに興味を持つかも