【React v19】use APIの基本的な使い方 ~Promise編~
はじめに
こんにちは!
今回はReact v19で新しく追加されたuse
APIのプロミス(Promise)に関する使い方についてアウトプットを兼ねてまとめたいと思います!
※記載されているデモコードの実行環境は以下になります
実行環境
"next": "15.2.0",
"react": "19.0.0",
"react-dom": "19.0.0",
"react-error-boundary": "^5.0.0"
記事を読むメリット
-
use
APIのプロミスに関する概要・使い方、メリットを知ることができる
結論
-
use
を使うことで、サーバコンポーネントから受け取ったプロミスの値を読み取ることができ、レンダーのブロックも防ぐことができる
プロミスから値を読み取る
以下のようにサーバコンポーネントからクライアントコンポーネントへpropsとしてプロミスを渡すことで、値を読み取ることができます。
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>
);
}
"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>
);
};
export type UsersType = {
id: string;
name: string;
}[];
Sespenseを使ってフォールバックを表示できる
以下のようにプロミスを受け取るクライアントコンポーネントをSespense
でラップすることでフォールバックを表示できます。
// ...
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
を使うことでレンダーがブロックされるのを回避できます。
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} />; // ✅クライアントコンポーネントへ値を渡す
}
"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された場合にフォールバックを表示することができます。
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]
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>
);
}
"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を一緒に使うことが多いと思うので組み合わせることでより柔軟な対応ができると感じました。
参考
-
期待されるエラー(サーバサイドのフォームバリデーションやリクエストの失敗などのアプリケーション通常動作時に発生する可能性のあるエラー)の場合は
useActionState
を使って適切にハンドリングする必要があります。(Handling expected errors) ↩︎ -
エラーは最も近い親のエラーバウンダリにバブルアップします。(Nested error boundaries) ↩︎
Discussion