💐

【React v19】use APIの基本的な使い方 ~Promise編~

2025/03/02に公開

はじめに

こんにちは!
今回はReact v19で新しく追加されたuseAPIのプロミス(Promise)に関する使い方についてアウトプットを兼ねてまとめたいと思います!

※記載されているデモコードの実行環境は以下になります

実行環境
"next": "15.2.0",
"react": "19.0.0",
"react-dom": "19.0.0",
"react-error-boundary": "^5.0.0"

記事を読むメリット

  • useAPIのプロミスに関する概要・使い方、メリットを知ることができる

結論

  • useを使うことで、サーバコンポーネントから受け取ったプロミスの値を読み取ることができ、レンダーのブロックも防ぐことができる

プロミスから値を読み取る

以下のようにサーバコンポーネントからクライアントコンポーネントへpropsとしてプロミスを渡すことで、値を読み取ることができます。

app/page.tsx
import { Suspense } from "react";
import { ErrorBoundary } from "react-error-boundary";
import { Users } from "./_components/Users";
import { UsersType } from "./_utils/types";

// サーバコンポーネント
export default function Page() {
  const test = true;

  const usersPromise: Promise<UsersType> = new Promise((resolve, reject) => {
    if (test) {
      const users: UsersType = [
        { id: "1", name: "hogehoge" },
        { id: "2", name: "fugafuga" },
      ];
      setTimeout(resolve, 3000, users);
    } else {
      reject("エラーが発生しました");
    }
  });

  return (
    <ErrorBoundary fallback={<p>⚠️Something went wrong!</p>}>
      <Suspense fallback={<p>Loading now...</p>}>
        {/* ✅プロミスをクライアントコンポーネントへ渡す */}
        <Users usersPromise={usersPromise} />
      </Suspense>
    </ErrorBoundary>
  );
}
app/_components/Users.tsx
"use client";

import { use } from "react";
import { UsersType } from "../_utils/types";

// クライアントコンポーネント
export const Users = ({
  usersPromise,
}: {
  usersPromise: Promise<UsersType>;
}) => {
  // ✅ここでプロミスを解決
  const users = use(usersPromise);

  return (
    <div>
      <h2>ユーザー一覧</h2>
      <ul>
        {users.map((user) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
};
app/_utils/types.ts
export type UsersType = {
  id: string;
  name: string;
}[];

Sespenseを使ってフォールバックを表示できる

以下のようにプロミスを受け取るクライアントコンポーネントをSespenseでラップすることでフォールバックを表示できます。

app/page.tsx
// ...

export default function Page() {
  // ...

  // ✅UsersをSuspenseでラップする
  return (
    <ErrorBoundary fallback={<p>⚠️Something went wrong!</p>}>
      <Suspense fallback={<p>Loading now...</p>}>
        <Users usersPromise={usersPromise} />
      </Suspense>
    </ErrorBoundary>
  );
}

async/awaitを使ってサーバコンポーネントでプロミスを解決した場合

下記のように、async/awaitを使ってサーバコンポーネントでプロミスを解決し、解決したデータをpropsとしてクライアントコンポーネントに渡すことも可能です。
しかし、awaitが終了するまでレンダーがブロックされるため、useを使うことでレンダーがブロックされるのを回避できます。

app/page.tsx
import { Users } from "./_components/Users";
import { UsersType } from "./_utils/types";

const fetchUsers = (): Promise<UsersType> => {
  return new Promise((resolve) => {
    const users = [
      { id: "1", name: "hogehoge" },
      { id: "2", name: "fugafuga" },
    ];
    setTimeout(resolve, 3000, users);
  });
};

export default async function Page() {
  // ✅サーバコンポーネントでPromiseを解決
  // ✅解決されるまでレンダーがブロックされる
  const users = await fetchUsers();

  return <Users users={users} />; // ✅クライアントコンポーネントへ値を渡す
}
app/_components/Users.tsx
"use client";

import { UsersType } from "../_utils/types";

export const Users = ({ users }: { users: UsersType }) => {
  return (
    <div>
      <h2>ユーザー一覧</h2>
      <ul>
        {users.map((user) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
};

プロミスが拒否された場合の扱い

  • コンポーネントをエラーバウンダリでラップする
  • プロミスの.catch()を使用する

コンポーネントをエラーバウンダリでラップする

以下のようにコンポーネントをエラーバウンダリでラップすることで、プロミスがrejectされた場合にフォールバックを表示することができます。

app/page.tsx
import { Suspense } from "react";
import { ErrorBoundary } from "react-error-boundary";
import { Users } from "./_components/Users";
import { UsersType } from "./_utils/types";

// サーバコンポーネント
export default function Page() {
  const test = false;

  const usersPromise: Promise<UsersType> = new Promise((resolve, reject) => {
    if (test) {
      const users: UsersType = [
        { id: "1", name: "hogehoge" },
        { id: "2", name: "fugafuga" },
      ];
      setTimeout(resolve, 3000, users);
    } else {
      reject("エラーが発生しました");
    }
  });

  return (
    {/* ✅コンポーネントをエラーバウンダリでラップする */}
    <ErrorBoundary fallback={<p>⚠️Something went wrong!</p>}>
      <Suspense fallback={<p>Loading now...</p>}>
        <Users usersPromise={usersPromise} />
      </Suspense>
    </ErrorBoundary>
  );
}

プロミスの.catch()を使用する

以下は公式ドキュメントの例ですが、.catch()で代替値を返すことで、プロミスの解決値として使用できます。

import { Message } from './message.js';

export default function App() {
  const messagePromise = new Promise((resolve, reject) => {
    reject();
  }).catch(() => {
    return "no new message found.";
  });

  return (
    <Suspense fallback={<p>waiting for message...</p>}>
      <Message messagePromise={messagePromise} />
    </Suspense>
  );
}

おまけ(Next.jsを使用した場合)

Next.jsのApp Routerを使用した場合はerror.tsxを作成することでthrowされた例外を捕捉することができます。[1]
以下のようにpage.tsxと同じ階層に作成することで子コンポーネント(User.tsx)の例外を捕捉できます。[2]

app/page.tsx
import { Suspense } from "react";
import { Users } from "./_components/Users";
import { UsersType } from "./_utils/types";

// サーバコンポーネント
export default function Page() {
  const test = false;

  const usersPromise: Promise<UsersType> = new Promise((resolve, reject) => {
    if (test) {
      const users: UsersType = [
        { id: "1", name: "hogehoge" },
        { id: "2", name: "fugafuga" },
      ];
      setTimeout(resolve, 3000, users);
    } else {
      reject("エラーが発生しました");
    }
  });

  return (
    <Suspense fallback={<p>Loading now...</p>}>
      <Users usersPromise={usersPromise} />
    </Suspense>
  );
}
app/error.tsx
"use client";

// ✅エラー時に表示される
export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  return (
    <div>
      <h2>{error.message}</h2> // エラーが発生しました
      <button onClick={() => reset()}>Try again</button>
    </div>
  );
}

まとめ

上記のように、useを使うことでクライアントコンポーネントでプロミスを解決することができ、プロミスの状態がpendingやrejectedの場合のフォールバックも簡単に実装することができます。
ただ、Next.jsを一緒に使うことが多いと思うので組み合わせることでより柔軟な対応ができると感じました。

参考

https://ja.react.dev/reference/react/use#providing-an-alternative-value-with-promise-catch
https://nextjs.org/docs/app/getting-started/fetching-data

脚注
  1. 期待されるエラー(サーバサイドのフォームバリデーションやリクエストの失敗などのアプリケーション通常動作時に発生する可能性のあるエラー)の場合はuseActionStateを使って適切にハンドリングする必要があります。(Handling expected errors↩︎

  2. エラーは最も近い親のエラーバウンダリにバブルアップします。(Nested error boundaries↩︎

Discussion