Claude CodeでReact開発を実戦投入する方法: コンポーネント設計からテストまで
Claude CodeでReact開発を速く安全に進める設計、Hooks、テスト、失敗例を実コードで解説。
React開発でClaude Codeを使うと、画面の初速は確かに上がります。 ただし、生成されたコンポーネントをそのまま積み上げると、状態が散らばり、propsが膨らみ、テストしづらいUIになりがちです。 本番チームで効かせるコツは「作らせる」より先に「境界、状態、検証方法」をClaude Codeへ渡すことです。
この記事では、Claude CodeでReact + TypeScriptのUIを作るときの実務的な進め方を、コンポーネント境界、Hooks、state、props、フォーム、データ取得、Testing Library、アクセシビリティ、パフォーマンスまで一続きで整理します。公式情報はReact公式ドキュメント、React Testing Library、MDNのARIA解説、Claude Code公式ドキュメントを基準にしてください。
関連する基礎はClaude Code TypeScript Tips、テストの広げ方はClaude Codeテスト戦略、アクセシビリティの観点はClaude Codeアクセシビリティ、レンダリング改善はClaude Codeパフォーマンス最適化も合わせて読むとつながります。
React開発でClaude Codeに任せる範囲
Claude Codeは、既存リポジトリを読んだうえでコードを編集できるエージェントです。エージェントとは「指示を受け、ファイルを読み、必要ならコマンドを実行し、変更案を作る開発の相棒」くらいに考えるとわかりやすいです。React開発では、ボタン1個の生成よりも、画面単位の分解、型の整理、テスト追加、レビュー観点の洗い出しで価値が出ます。
ただし、Claude Codeに「いい感じの管理画面を作って」と頼むだけでは、見た目は整っていても運用に弱いコードが出やすくなります。生産チームでは、先に次の4点を渡します。
| 渡す情報 | 例 | Claude Codeの出力が安定する理由 |
|---|---|---|
| 画面の責務 | 一覧、検索、編集、保存エラー表示 | コンポーネントを過剰分割しにくい |
| 状態の置き場 | URL、親コンポーネント、custom hook、server cache | useStateの乱立を防げる |
| データ契約 | User型、APIレスポンス、空状態 | propsとテストが具体化する |
| 検証方法 | npm test、Testing Library、キーボード操作 | 生成後に壊れていない証拠を残せる |
用語も最初に揃えておきます。propsは「親から子へ渡す入力」です。stateは「画面が覚えている値」です。custom hookは「状態や副作用のロジックを再利用する関数」です。アクセシビリティは「キーボードやスクリーンリーダーでも使えること」です。こうした言葉を平易に定義してから依頼すると、チーム内のレビューも楽になります。
3つ以上の実ユースケース
1つ目は、社内管理画面の一覧です。ユーザー、請求、問い合わせ、権限などのテーブルは、検索、ソート、空状態、エラー、ローディングが毎回似ています。Claude Codeには「既存のTableコンポーネントを使う」「API型は変えない」「行クリックと編集ボタンのイベントを分ける」と指示します。
2つ目は、フォームの改善です。問い合わせフォーム、プロフィール編集、申込フォームでは、入力値、バリデーション、送信中、失敗時の再送が絡みます。React Hook Formの記事と組み合わせると、Claude Codeに既存schemaやエラーメッセージのルールを読ませてから実装できます。
3つ目は、データ取得画面です。検索結果、ダッシュボード、通知一覧では、server state、つまり「サーバーにある状態」をUIの一時状態と分ける必要があります。TanStack Queryを使うならClaude Code TanStack Query、小さなクライアント状態ならClaude Code Zustandが参考になります。
4つ目は、既存コンポーネントのレビューです。Claude Codeに「新規作成」ではなく「差分を読んで、責務の混在、不要なEffect、アクセシビリティ漏れ、テスト不足を指摘して」と依頼します。Masaが実際に記事サイトのUI改善で試したときも、新規生成よりレビュー依頼のほうが、余計なファイル追加を防ぎやすく、修正範囲が小さく済みました。
コンポーネント境界の考え方
React公式の「Thinking in React」は、UIを部品に分け、状態の最小単位を探す考え方です。Claude Codeにも同じ順番で依頼します。最初からファイルを大量生成させるのではなく、画面を次のように分けます。
UserAdminPage
-> UserFilters: 検索語とロール絞り込み
-> UserTable: 一覧表示、ソート、選択
-> UserStatusBadge: 状態の見た目
-> UserEditDialog: 編集フォーム
-> useUsers: データ取得と更新
境界の目安は「propsを説明できるか」です。UserStatusBadgeはstatusだけで説明できます。一方、UserTableに検索語、API呼び出し、モーダル開閉、保存処理まで入るなら大きすぎます。Claude Codeには「UI部品」「状態ロジック」「データ取得」を混ぜないように明示します。
コピペで動く型付きReactコンポーネント
次の例は、Vite + React + TypeScriptでそのまま使える小さな一覧コンポーネントです。Tailwindなしでも動くようにstyleをインラインにしています。Claude Codeにはこの粒度のサンプルを基準に「既存のデザインシステムへ置き換えて」と頼むと、見た目だけの巨大コンポーネントを避けやすくなります。
import type { FormEvent } from "react";
export type UserRole = "admin" | "editor" | "viewer";
export type User = {
id: string;
name: string;
email: string;
role: UserRole;
active: boolean;
};
type UserTableProps = {
users: User[];
selectedRole: "all" | UserRole;
onRoleChange: (role: "all" | UserRole) => void;
onToggleActive: (id: string) => void;
};
export function UserTable({
users,
selectedRole,
onRoleChange,
onToggleActive,
}: UserTableProps) {
const filteredUsers =
selectedRole === "all"
? users
: users.filter((user) => user.role === selectedRole);
function handleRoleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
const formData = new FormData(event.currentTarget);
onRoleChange(formData.get("role") as "all" | UserRole);
}
return (
<section aria-labelledby="user-table-title">
<h2 id="user-table-title">Team members</h2>
<form onSubmit={handleRoleSubmit} style={{ marginBottom: 12 }}>
<label htmlFor="role">Filter by role </label>
<select id="role" name="role" defaultValue={selectedRole}>
<option value="all">All</option>
<option value="admin">Admin</option>
<option value="editor">Editor</option>
<option value="viewer">Viewer</option>
</select>
<button type="submit">Apply</button>
</form>
{filteredUsers.length === 0 ? (
<p role="status">No users match this filter.</p>
) : (
<table>
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Email</th>
<th scope="col">Role</th>
<th scope="col">Status</th>
<th scope="col">Action</th>
</tr>
</thead>
<tbody>
{filteredUsers.map((user) => (
<tr key={user.id}>
<td>{user.name}</td>
<td>{user.email}</td>
<td>{user.role}</td>
<td>{user.active ? "Active" : "Paused"}</td>
<td>
<button type="button" onClick={() => onToggleActive(user.id)}>
{user.active ? "Pause" : "Activate"}
</button>
</td>
</tr>
))}
</tbody>
</table>
)}
</section>
);
}
このコードで大事なのは、UserTableがAPIを呼ばないことです。受け取ったusersを表示し、イベントを親へ返すだけにしています。propsが明確なので、Claude Codeがテストを書きやすく、デザイン差し替えもしやすい構造になります。
custom hookでデータ取得を分離する
custom hookは、コンポーネントから状態管理や副作用を外へ逃がすための関数です。副作用とは、画面表示そのものではなく、API通信、localStorage、タイマーなど外部に影響する処理です。次のhookはAbortControllerで古いリクエストを止め、unmount後のstate更新を避けます。
import { useEffect, useState } from "react";
import type { User } from "./UserTable";
type UsersState =
| { status: "loading"; users: User[]; error: null }
| { status: "success"; users: User[]; error: null }
| { status: "error"; users: User[]; error: string };
export function useUsers(endpoint: string) {
const [state, setState] = useState<UsersState>({
status: "loading",
users: [],
error: null,
});
useEffect(() => {
const controller = new AbortController();
async function loadUsers() {
setState({ status: "loading", users: [], error: null });
try {
const response = await fetch(endpoint, { signal: controller.signal });
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const users = (await response.json()) as User[];
setState({ status: "success", users, error: null });
} catch (error) {
if (error instanceof DOMException && error.name === "AbortError") return;
setState({
status: "error",
users: [],
error: error instanceof Error ? error.message : "Unknown error",
});
}
}
void loadUsers();
return () => controller.abort();
}, [endpoint]);
return state;
}
本番ではTanStack Queryなどのライブラリを使う場面も多いですが、Claude Codeに設計を頼むときは、このような小さなhookで「UIと通信を分ける」意図を示すと安定します。
Testing Libraryでユーザー視点のテストを書く
Testing Libraryは、実装の内部ではなく、ユーザーが見つける文字、role、labelを手がかりにテストを書く考え方です。次のテストは、表示、フィルタ、ボタン操作を確認します。
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, expect, it, vi } from "vitest";
import { UserTable, type User } from "./UserTable";
const users: User[] = [
{ id: "1", name: "Masa", email: "masa@example.com", role: "admin", active: true },
{ id: "2", name: "Aiko", email: "aiko@example.com", role: "viewer", active: false },
];
describe("UserTable", () => {
it("filters users and toggles active status", async () => {
const user = userEvent.setup();
const onRoleChange = vi.fn();
const onToggleActive = vi.fn();
render(
<UserTable
users={users}
selectedRole="all"
onRoleChange={onRoleChange}
onToggleActive={onToggleActive}
/>,
);
await user.selectOptions(screen.getByLabelText(/filter by role/i), "viewer");
await user.click(screen.getByRole("button", { name: /apply/i }));
await user.click(screen.getByRole("button", { name: /activate/i }));
expect(screen.getByText("Masa")).toBeInTheDocument();
expect(screen.getByText("Aiko")).toBeInTheDocument();
expect(onRoleChange).toHaveBeenCalledWith("viewer");
expect(onToggleActive).toHaveBeenCalledWith("2");
});
});
Claude Codeにテストを書かせるときは「スナップショットだけで済ませない」「roleとlabelで取得する」「失敗時の空状態も入れる」と指定します。これだけでアクセシビリティの抜けも見つかりやすくなります。
Claude Codeへのレビュー依頼プロンプト
実装プロンプトより重要なのがレビュー依頼です。次のプロンプトは、余計な新規作成を抑え、既存差分の品質確認に寄せています。
React + TypeScriptの差分をレビューしてください。
観点:
- コンポーネント境界が大きすぎないか
- propsが増えすぎていないか
- useEffectが不要な計算やイベント処理に使われていないか
- stateとserver stateが混ざっていないか
- フォームのlabel、エラー表示、キーボード操作が成立するか
- Testing Libraryでユーザー視点のテストがあるか
- 大量データで再レンダリングが増えすぎないか
制約:
- 新しいライブラリは提案だけにしてください
- 変更が必要な場合は最小差分にしてください
- 最後に `npm test` と `npm run build` で確認できるチェックリストを出してください
よくある失敗例と落とし穴
落とし穴の1つ目は、コンポーネントを細かくしすぎることです。UserNameText、UserEmailText、UserRoleTextのような部品が増えると、ファイル数は増えるのに責務は明確になりません。分割は「再利用」「状態の分離」「テストしやすさ」のどれかに効くときだけで十分です。
2つ目は、useEffectの使いすぎです。フィルタ済み配列や表示ラベルのようにpropsとstateから計算できる値は、Effectではなくレンダリング中に計算します。React公式の「You Might Not Need an Effect」はClaude Codeにも参照させたいページです。
3つ目は、フォームのアクセシビリティ漏れです。placeholderだけで入力欄を説明する、エラーを色だけで伝える、送信中にボタンの状態が変わらない、という失敗がよくあります。label、aria-describedby、role="status"、キーボード操作を確認してください。
4つ目は、AIが作った「過剰に立派なコンポーネント」です。まだ1画面でしか使わないのに汎用Table、汎用Modal、汎用FilterEngineを作ると、将来の変更が重くなります。Claude Codeには「今必要な抽象化だけ」「2箇所目が出るまで汎用化しない」と指示します。
5つ目は、動作確認の証拠がないことです。コードがきれいでも、テスト、ビルド、手動確認が残っていなければ本番チームでは不十分です。レビュー時には「何を実行し、何が通ったか」を必ず書かせます。
パフォーマンスとアクセシビリティを後回しにしない
Reactのパフォーマンス改善は、最初からmemoを貼ることではありません。まず、stateを必要な場所に置く、リストのkeyを安定させる、大きな配列のフィルタを必要なときだけ行う、入力中に重い処理を走らせない、という基本を守ります。Claude Codeに「なぜ再レンダリングが起きるかを説明してから修正して」と頼むと、不要なuseMemoの量産を避けやすくなります。
アクセシビリティも同じです。ARIAを大量に足すより、まずbutton、label、table、th scope="col"のような正しいHTMLを使うほうが堅実です。MDNのARIA解説にもある通り、ARIAは不足を補う道具であり、ネイティブHTMLの代替ではありません。
CTA: チームのReact開発ルールに落とし込む
個人で試すだけなら、この記事のプロンプトとコード例をそのまま使えば十分です。チームで本番導入するなら、CLAUDE.mdにコンポーネント境界、テストコマンド、アクセシビリティ基準、禁止する過剰生成を書き、レビュー依頼プロンプトをテンプレート化してください。
ClaudeCodeLabでは、既存Reactリポジトリを題材にしたClaude Code研修・相談で、画面分解、Hooks設計、Testing Library、レビュー運用、収益導線の改善まで一緒に整理できます。記事メディアやSaaSのUIでは、単に開発速度を上げるだけでなく、問い合わせ、教材販売、相談予約につながるフォームやCTAの品質も重要です。
実際に試した結果
この記事のサンプルは、Vite + React + TypeScript + Vitest + Testing Libraryを想定して構文を確認しながら作りました。Masaが記事サイトの管理UI改善で同じ流れを使ったときは、最初にClaude Codeへ「境界」「状態」「テスト観点」を渡したほうが、後から修正するpropsの数が少なく、レビューで指摘されたアクセシビリティ漏れも早く潰せました。結論として、React開発でClaude Codeを本番投入するなら、生成量よりも検証可能な小さな差分を重視するのが一番安定します。
無料PDF: Claude Code はじめてのチートシート
まずは無料PDFで基本コマンドと最初の使い方をまとめて確認してください。登録後はそのままテンプレート集や導入相談にも進めます。
スパムは送りません。登録情報は厳重に管理します。
Claude Codeを仕事で使える形にしませんか?
無料PDFで基礎を固めたあと、すぐ使えるテンプレート集で試し、必要なら業務自動化や導入相談まで進められます。
この記事を書いた人
Masa
Claude Codeの実務活用、導入設計、収益導線改善を検証しているエンジニア。10言語の技術メディアを運営中。
関連書籍・参考図書
この記事のテーマに関連する書籍を楽天ブックスで探せます。
※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。
関連記事
ObsidianメモをCLAUDE.mdに変えるClaude Code運用: 文脈を毎回説明しない仕組み
Obsidianの作業メモからCLAUDE.md用の運用ノートを作り、Claude Codeに安定した文脈を渡す方法。
Claude Code Revenue CTA Routing: 記事からPDF、Gumroad、相談へ送る設計
PVだけで終わらせず、読者の状態に合わせて無料PDF、Gumroad教材、導入相談へ分岐するCTA設計です。
Claude Codeチーム引き継ぎルール: レビュー、権限、収益導線まで渡す実務手順
Claude Codeの作業をチームで渡すための証拠、権限、ロールバック、無料PDF/Gumroad/相談導線の実務ルール。