14
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

KDDIアジャイル開発センター Engineer & DesignerAdvent Calendar 2022

Day 11

React18の新機能 Suspense機能を使ってローディング画面とエラー画面を表示してみよう

Posted at

はじめに

こんにちは、都内でソフトウェアエンジニアをしているYSasagoと申します。
React18で、Suspense機能が追加されました。そこで、Suspense機能についてまとめようかと思います。

  • Suspense機能を使うことで、API実行時などのローディング状態の記述を上位のコンポーネントで宣言できる
  • Suspense機能を適切に分割して使うことでユーザービリティの向上をつなげることができる

サスペンスにより、コンポーネントツリーの一部がまだ表示できない場合に、ロード中という状態を宣言的に記述できるようになります:

サンプルアプリの作成

Suspense機能を試すために、jsonplaceholderalbumsを取得して、画面に表示するサンプルアプリを作成しました。

FireShot Capture 116 - React App - localhost.png

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

const queyClient = new QueryClient();

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

reportWebVitals();

src/App.tsx
import "./App.css";
import { ReactQuery } from "./components/ReactQuery";

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

export default App;

src/components/ReactQuery.tsx
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 ReactQuery = () => {
  const { isLoading, error, data } = useQuery<Album[]>(["albums"], fetchAlbums);

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

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

読込中の画面表示を、Suspense機能を使って実装してみる

useQueryのisLoadingでローディング画面を実装していた箇所を、Suspense機能を使うように変更します。
react-queryのライブラリは、Suspense機能に対応しているので、有効化するだけで使用することができます。

src/App.tsx
import { Suspense } from "react"; // 追加
import "./App.css";
import { ReactQuery } from "./components/ReactQuery";

function App() {
  return (
    <div className="App">
      {/* fallbackにはローディング中に表示する画面を渡す*/}
      <Suspense fallback={<p>Loading...</p>}>
        <ReactQuery />
      </Suspense>
    </div>
  );
}

export default App;
src/components/ReactQuery.tsx
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 ReactQuery = () => {
  const { isLoading, error, data } = useQuery<Album[]>(
    ["albums"],
    fetchAlbums,
    { suspense: true } // 追加: Suspense機能を有効化する
  );

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

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

Suspense機能を使ってローディング画面を作成することができました。
スクリーンショット 2022-11-06 17.25.29.png

エラーの画面表示を、Suspense機能を使って実装してみる

エラーの画面表示を、Suspense機能を使って実装しようと思います。

最初に、ライブラリのreact-error-boundaryを追加します。

$ yarn add react-error-boundary
src/App.tsx
import { Suspense } from "react";
import "./App.css";
import { ReactQuery } from "./components/ReactQuery";
import { ErrorBoundary } from "react-error-boundary"; // 追加

function App() {
  return (
    <div className="App">
      {/* fallbackにエラー画面のUIを渡してあげる */}
      <ErrorBoundary fallback={<p>error</p>}>
        <Suspense fallback={<p>Loading...</p>}>
          <ReactQuery />
        </Suspense>
      </ErrorBoundary>
    </div>
  );
}

export default App;
src/components/ReactQuery.tsx
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あ" // エラーが表示されるように無効なURLを指定する
  );
  return result.data;
};
export const ReactQuery = () => {
  const { data } = useQuery<Album[]>(["albums"], fetchAlbums);

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

Suspense機能を用いて、エラー画面が表示されました。
スクリーンショット 2022-11-06 17.42.37.png

複数のSuspenseを使う場合

複数のSuspenseを使って、書くとユーザービリティの向上につながります。
よって、下記のコードの様にコンポーネントごとに、適切にSuspenseErrorBoundaryと書くと良いでしょう。

export const ReactQuery = () => {
  return (
    <div style={{ display: "flex", padding: "16px" }}>
      <Sidebar />
      <div style={{ flexGrow: 1 }}>
        {/* コンポーネント毎にErrorBoundaryとSuspenseを使う */}
        <ErrorBoundary fallback={<p>Album list error</p>}>
          <Suspense fallback={<p>Album list Loading...</p>}>
            <AlbumList />
          </Suspense>
        </ErrorBoundary>
        {/* コンポーネント毎にErrorBoundaryとSuspenseを使う */}
        <ErrorBoundary fallback={<p>Todo list error</p>}>
          <Suspense fallback={<p>Todo list Loading...</p>}>
            <TodoList />
          </Suspense>
        </ErrorBoundary>
      </div>
    </div>
  );
};

複数のSuspenseを使う場合は、ネストが深いものが最優先されます。

まとめ

  • React18で追加されたSuspense機能を手元で動かしてみました
  • Suspense機能を導入するとはユーザービリティの向上につながるので、プロジェクトに合わせて導入したいですね

参考

14
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?