web-framework-react について
React 開発者向けのコンポーネントとフックのパターンを扱うスキルで、React 19 を中心に解説します。フォーム、ref、楽観的更新、エラーバウンダリーに加え、ブラウザーとサーバー描画向けのスタイル非依存 API を扱います。
主な機能
- 01React 19 の ref プロパティ化とクリーンアップ
- 02useActionState と useFormStatus によるフォーム状態管理
- 03楽観的更新と Promise の読み取り
- 04再利用可能なコンポーネント、バリアント、カスタムフック
- 05エラーバウンダリーと実行時の注意点
活用シーン
- 01forwardRef から ref プロパティへのコンポーネント移行
- 02保留状態とエラー状態を持つフォームの構築
- 03楽観的 UI 更新と再試行可能なエラーバウンダリーの追加
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ agents-inc/ skills --skill web-framework-reactインストール後、Claude Code で/web-framework-reactを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
metadata.yamlreference.mdexamples/core.mdhooks.mdreact-19-hooks.md詳細
React パターン
クイックガイド: React 19 では、このスキルが有効にする 4 つの事項が確定しています。
refは通常の props であり、forwardRefは不要です。フォームの送信状態にはuseActionStateを使用します。useFormStatusは子コンポーネントからのみ、親フォームの状態を取得できます。また、ref コールバックはクリーンアップ関数を返せます。コンポーネントはclassNameを公開し、バリアントをdata-*属性で表現することで、スタイルに依存しない状態を保ちます。
詳細なリソース:
- examples/core.md — コンポーネントの構成、バリアント props、イベントハンドラー、アクセシブルなコントロール
- examples/hooks.md — usePagination、useDebounce、useLocalStorage
- examples/react-19-hooks.md — useActionState、useFormStatus、useOptimistic、use()、ref のクリーンアップ
- reference.md — 判断フローチャート、修正コード付きのアンチパターン、コンポーネントとフックのチェックリスト
該当するパス
- ブラウザーで React をレンダリングする場合 — 以下のすべてのパターンがそのまま適用され、フォームの送信には examples/react-19-hooks.md の
useActionStateを使用します。 - サーバーフレームワークで React をレンダリングする場合 — 送信とデータ読み込みには、そのフレームワーク独自のサーバープリミティブを使用するため、パターン 6 は省略し、コンポーネント、フック、ref、エラーバウンダリーのパターンはそのまま適用します。
<critical_requirements>
React コードを書く前に
ref は通常の props として渡します。 React 19 は forwardRef なしで ref を転送するため、ラッパーと手動の displayName が不要になります。
再利用可能なすべてのコンポーネントで className を公開します。 このスキルが把握していないコンポーネントのスタイルを、利用側が指定するための接点はこれだけです。
useFormStatus は <form> の内側にレンダリングされるコンポーネントから呼び出します。 フォームをレンダリングするコンポーネント自体で呼び出すと、エラーも表示されず、pending: false がずっと返されます。
フォームに保留中またはエラーの状態が必要な場合は、useActionState を使います。 どちらの状態も扱え、<form action={...}> はハイドレーション前でも機能します。
</critical_requirements>
自動検出: React 19、useActionState、useFormStatus、useOptimistic、use()、props としての ref、ref のクリーンアップ、forwardRef の移行、React.ComponentProps、getDerivedStateFromError、componentDidCatch、useCallback、カスタムフック
適用対象:
- コンポーネントの props、合成、バリアント API
forwardRefコンポーネントから ref-as-prop への移行- React 19 のフックを使ったフォーム送信、楽観的更新、Promise の読み取り
- 状態を再利用可能なロジックとして扱うカスタムフック
- リトライ機能付きエラーバウンダリー
対象外:
- スタイリング — コンポーネントは
classNameを受け取り、バリアントをdata-*として公開します。それらを埋める CSS の手法は対象外です - コンポーネントの存続期間を超えるクライアント状態、およびキャッシュと無効化を伴うサーバーデータ — コンポーネントは両方を props として受け取り、その取得元を意識しません
- ルーティング、およびルートが読み込むデータ
- ネットワークのテスト用ダブル
コアパターン
パターン 1:コンポーネントの構成
再利用可能なコンポーネントは、要素自身の props を展開し、ref を直接受け取り、どのスタイリング層からも指定できる data-* 属性でバリアントを表現します。
export type ButtonProps = React.ComponentProps<"button"> & {
variant?: "default" | "ghost" | "link";
size?: "default" | "large" | "icon";
ref?: React.Ref<HTMLButtonElement>;
};
export function Button({ variant = "default", size = "default", className, ref, ...props }: ButtonProps) {
return <button className={className} data-variant={variant} data-size={size} ref={ref} {...props} />;
}
コード全文:examples/core.md
パターン 2:型付きバリアント props
視覚的な軸が 2 つ以上あるコンポーネントには、バリアント props を用意します。視覚スタイルが 1 つのコンポーネントが受け取るのは className のみです。メンバーが 1 つだけのユニオンは、2 つ目のケースを持たない抽象化です。
export type AlertVariant = "info" | "warning" | "error" | "success";
export function Alert({ variant = "info", className, ref, ...props }: AlertProps) {
return <div ref={ref} className={className} data-variant={variant} {...props} />;
}
コード全文:examples/core.md
パターン 3:イベントハンドラーの命名
内部ハンドラーには handle を付け(handleNameChange)、コールバック prop には on を付けます(onSelect)。また、各ハンドラーでイベント型(FormEvent<HTMLFormElement>、ChangeEvent<HTMLInputElement>)を指定することで、誤ったフィールドへのアクセスをコンパイル時に検出できます。
コード全文:examples/core.md
パターン 4:カスタムフック
フックを呼び出し、何もレンダリングしないロジックは、コンポーネントではなく、use を先頭に付けたフックにします。ページネーションの状態、デバウンスされた値、永続化された設定などが該当します。
コード全体:examples/hooks.md
パターン 5:再試行に対応したエラーバウンダリ
エラーバウンダリはレンダリングエラーを捕捉し、リセット関数を返すため、一時的な障害の影響はページ全体ではなく、1 つのセクションにとどまります。ルートだけでなく、各機能領域を囲むように配置します。
interface Props {
children: ReactNode;
fallback?: (error: Error, reset: () => void) => ReactNode;
onError?: (error: Error, errorInfo: ErrorInfo) => void;
}
getDerivedStateFromError にはフック形式がないため、エラーバウンダリはクラスです。配置、復旧、フォールバックの設計はそれぞれ独立したテーマであり、このスキルの範囲外で扱います。
パターン 6:フォーム送信に useActionState を使用する
このフックは、アクションの直近の結果、<form action={...}> に渡すアクション、保留状態を示すフラグを返します。これにより、3 つの useState 呼び出しと、それらのリセット処理を置き換えられます。
async function updateProfile(prevState: string | null, formData: FormData) {
try {
await saveProfile({ name: formData.get("name") as string });
return null;
} catch {
return "Failed to save profile";
}
}
const [error, submitAction, isPending] = useActionState(updateProfile, null);
コード全体:examples/react-19-hooks.md
パターン 7:送信ボタンに useFormStatus を使用する
送信ボタンは、親フォームの保留状態を自分で読み取るため、1 つのボタンコンポーネントをすべてのフォームで使えます。また、フォームからフラグを渡す必要もありません。
import { useFormStatus } from "react-dom";
function SubmitButton({ children }: { children: React.ReactNode }) {
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending} aria-busy={pending}>
{pending ? "Submitting..." : children}
</button>
);
}
コード全体:examples/react-19-hooks.md
パターン 8:即時のフィードバックに useOptimistic を使用する
期待される結果をすぐにレンダリングし、リクエストが失敗した場合は React に元に戻させます。セッターは startTransition の中で呼び出します。
const [optimisticItems, addOptimistic] = useOptimistic(
items,
(state, update: Item) => [...state, { ...update, pending: true }],
);
startTransition(async () => {
addOptimistic(newItem);
await saveItem(newItem);
});
コード全体:examples/react-19-hooks.md
パターン 9:プロミスとコンテキストに use() を使用する
use() はレンダリング中にプロミスまたはコンテキストを読み取り、useContext とは異なり、早期リターンの後でも呼び出せます。プロミスは解決するまでサスペンドするため、呼び出し元を <Suspense> の内側に置き、拒否された場合はエラーバウンダリで捕捉します。
function Comments({ commentsPromise }: { commentsPromise: Promise<Comment[]> }) {
const comments = use(commentsPromise);
return <ul>{comments.map((c) => <li key={c.id}>{c.text}</li>)}</ul>;
}
<Suspense fallback={<p>Loading...</p>}>
<Comments commentsPromise={fetchComments()} />
</Suspense>
コード全体:examples/react-19-hooks.md
パターン 10:ref コールバックのクリーンアップ
ref コールバックはクリーンアップ関数を返すことができ、アンマウント時に実行されます。これにより、DOM のセットアップに使う useRef と useEffect の組み合わせを置き換えられます。
<video
ref={(video) => {
if (!video) return;
video.play();
return () => {
video.pause();
video.currentTime = 0;
};
}}
src={src}
/>
コード全体:examples/react-19-hooks.md
<red_flags>
要注意事項
実行時に問題が起きます:
<form>をレンダリングするコンポーネントでuseFormStatusを呼び出すと、pendingは false のままでボタンが無効になりません。子コンポーネントから呼び出してくださいtry/catchの中でuse()を呼び出すと、サスペンドのために例外が投げられ、catch によってサスペンドが握りつぶされます。代わりに呼び出し元をエラーバウンダリで囲んでください- トランジションまたはフォームアクションの外で
useOptimisticのセッターを呼び出すと、React によって呼び出しが拒否され、楽観的な値が反映されません - SSR のレンダリング中にブラウザー API を読み取る場合は、
typeof window !== "undefined"で保護するか、読み取りをエフェクト内に移動してください - イベントハンドラー、非同期処理、SSR のエラーをエラーバウンダリで捕捉しようとしても、捕捉できるのはレンダリングエラーだけです。そのため、これらの処理経路では独自に
try/catchを実装してください
予想外の動作:
- React 19 では、クリーンアップ関数以外を返す ref コールバックは型エラーになります。また、アンマウント時にコールバックへ
nullが渡されることもなくなりました useOptimisticは失敗時に自動で元に戻すため、手動でロールバックすると競合します- メモ化されていない子コンポーネントに渡すハンドラーを
useCallbackで囲んでも、メモリー割り当てのコストがかかるだけで効果はありません。どちらにしても React Compiler により、そのラッパーは不要になります forwardRefは引き続きコンパイルされるため、ラッパーとそのdisplayNameが無駄になっても警告されませんclassNameを持たないコンポーネントは、利用側でスタイルを設定できません
</red_flags>