react-patterns について
React 19で開発するエンジニア向けのパターン集です。Next.js App Routerを含む開発で、フック、Server Actions、非同期UI、パフォーマンスの指針を整理しています。
主な機能
- 01React 19 APIとフックのパターン
- 02Server/Client Componentの指針
- 03楽観的UIとServer Actions
- 04Suspenseと非同期レンダリング
- 05状態管理とパフォーマンスのパターン
活用シーン
- 01Next.js App Routerアプリの構築
- 02検証付きServer Actionフォームの実装
- 03データ変更への楽観的な応答
- 04複雑なコンポーネント状態に使うフックの選択
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ giuseppe-trisciuoglio/ developer-kit --skill react-patternsインストール後、Claude Code で/react-patternsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/component-patterns.mdhooks-patterns.mdlearn.mdperformance-patterns.mdreact19-features.mdreference.mdtypescript-patterns.md詳細
React 19 の開発パターン
概要
Next.js App Router、Server Actions、楽観的 UI、並行機能に対応した React 19 のパターンです。API の概要はクイックリファレンスを、コピーして使えるパターンは例を参照してください。
使用する場面
- Next.js App Router で React 19 アプリケーションを構築する
useOptimisticまたはuseTransitionで楽観的 UI を実装する- フォーム検証を伴う Server Actions を作成する
- クラスコンポーネントからフックに移行する
- React Compiler で並行レンダリングを最適化する
useReducerまたはカスタムフックで複雑な状態を管理する- 非同期処理を Suspense 境界でラップする
クイックリファレンス
| パターン | フック/API | 使用例 |
|---|---|---|
| ローカル状態 | useState | シンプルなコンポーネントの状態 |
| 複雑な状態 | useReducer | 複数のアクションを持つ状態マシン |
| 副作用 | useEffect | サブスクリプション、データ取得 |
| 共有状態 | useContext / createContext | コンポーネント間のデータ |
| DOM へのアクセス | useRef | フォーカス、測定、タイマー |
| パフォーマンス | useMemo / useCallback | コストの高い計算 |
| 緊急性の低い更新 | useTransition | 大きなリストの検索/フィルター |
| コストの高い UI の遅延 | useDeferredValue | 更新中も古い値を表示 |
| リソースの読み取り | use() (React 19) | レンダリング中の Promise とコンテキスト |
| 楽観的 UI | useOptimistic (React 19) | ミューテーションへの即時フィードバック |
| フォームの状態 | useFormStatus (React 19) | 子コンポーネントの保留状態 |
| フォームの状態 | useActionState (React 19) | Server Action の結果 |
| 自動メモ化 | React Compiler | 手動でのメモ化/コールバックを不要にします |
手順
- コンポーネントの種類を特定する:Server Component と Client Component のどちらが必要か判断します
- フックを選択する:状態管理と副作用に適したフックを使用します
- Props の型を定義する:すべてのコンポーネントの Props に TypeScript インターフェースを定義します
- 非同期処理を扱う:データ取得を行うコンポーネントを Suspense 境界でラップします
- 最適化する:コストの高いレンダリングには React Compiler または手動のメモ化を使用します
- エラーを処理する:エラーを適切に処理するために ErrorBoundary を追加します
- Server Actions を検証する:Zod/スキーマ検証を定義してから、次のテストを行います
- 無効な入力を送信 → 拒否されることを確認します
- 有効な入力を送信 → 成功することを確認します
例
クライアントとのインタラクションを伴う Server Component
// Server Component (default) — async, fetches data
async function ProductPage({ id }: { id: string }) {
const product = await db.product.findUnique({ where: { id } });
return (
<div>
<h1>{product.name}</h1>
<AddToCartButton productId={product.id} />
</div>
);
}
// Client Component — handles interactivity
'use client';
function AddToCartButton({ productId }: { productId: string }) {
const [isPending, startTransition] = useTransition();
const handleAdd = () => {
startTransition(async () => {
await addToCart(productId);
});
};
return (
<button onClick={handleAdd} disabled={isPending}>
{isPending ? 'Adding...' : 'Add to Cart'}
</button>
);
}
即時フィードバックのための useOptimistic
'use client';
import { useOptimistic } from 'react';
function TodoList({ todos, addTodo }: { todos: Todo[]; addTodo: (t: Todo) => Promise<void> }) {
const [optimisticTodos, addOptimisticTodo] = useOptimistic(
todos,
(state, newTodo: Todo) => [...state, { ...newTodo, pending: true }]
);
const handleSubmit = async (formData: FormData) => {
const newTodo = { id: Date.now(), text: formData.get('text') as string };
addOptimisticTodo(newTodo); // Immediate UI update
await addTodo(newTodo); // Actual backend call
};
return (
<form action={handleSubmit}>
{optimisticTodos.map(todo => (
<div key={todo.id} style={{ opacity: todo.pending ? 0.5 : 1 }}>
{todo.text}
</div>
))}
<input type="text" name="text" />
<button type="submit">Add</button>
</form>
);
}
フォームを使った Server Action
// app/actions.ts
'use server';
import { z } from 'zod';
import { revalidatePath } from 'next/cache';
const schema = z.object({
title: z.string().min(5),
content: z.string().min(10),
});
export async function createPost(prevState: any, formData: FormData) {
const parsed = schema.safeParse({
title: formData.get('title'),
content: formData.get('content'),
});
if (!parsed.success) {
return { errors: parsed.error.flatten().fieldErrors };
}
await db.post.create({ data: parsed.data });
revalidatePath('/posts');
return { success: true };
}
// app/blog/new/page.tsx
'use client';
import { useActionState } from 'react';
import { createPost } from '../actions';
export default function NewPostPage() {
const [state, formAction, pending] = useActionState(createPost, {});
return (
<form action={formAction}>
<input name="title" placeholder="Title" />
{state.errors?.title && <span>{state.errors.title[0]}</span>}
<textarea name="content" placeholder="Content" />
<button type="submit" disabled={pending}>
{pending ? 'Publishing...' : 'Publish'}
</button>
</form>
);
}
カスタムフック
export function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(true);
useEffect(() => {
function handleOnline() { setIsOnline(true); }
function handleOffline() { setIsOnline(false); }
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
return isOnline;
}
緊急性の低い更新のための useTransition
function SearchableList({ items }: { items: Item[] }) {
const [query, setQuery] = useState('');
const [isPending, startTransition] = useTransition();
const [filteredItems, setFilteredItems] = useState(items);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setQuery(e.target.value);
startTransition(() => {
setFilteredItems(items.filter(i => i.name.toLowerCase().includes(e.target.value.toLowerCase())));
});
};
return (
<div>
<input value={query} onChange={handleChange} />
{isPending && <span>Filtering...</span>}
<ul>{filteredItems.map(i => <li key={i.id}>{i.name}</li>)}</ul>
</div>
);
}
ベストプラクティス
Server と Client の選択
- Server Component から始めます(ディレクティブは不要です)
'use client'を追加するのは、フック、ブラウザー API、イベントハンドラーを使用する場合に限ります
状態管理
- 状態は最小限に保ちます。派生値はエフェクト内ではなく、レンダリング中に計算します
- 関連するアクションが複数ある状態には
useReducerを使用します - 状態を最も近い共通の祖先へ引き上げます
エフェクト
- エフェクトは外部システムとの同期にのみ使用します
- 正しい依存配列を必ず指定します
- サブスクリプションやタイマーにはクリーンアップ関数を返します
- 状態を直接変更してはいけません。必ず新しい参照を作成します
パフォーマンス
- React Compiler を使用する場合:手動の
useMemo、useCallback、memoは避けます - React Compiler を使用しない場合:コストの高い計算には
useMemoを、安定したコールバックにはuseCallbackを使用します - 優先度の低い状態更新には
useTransitionを使用します - リストのキーには配列のインデックスではなく、安定した ID を使用します
React 19 固有の注意点
use(promise)を使うコンポーネントは Suspense 境界でラップします- フォームと Server Action の連携には
useActionStateを使用します - Server Action の入力を検証します。これらは公開エンドポイントです
- Server Component から Client Component には、シリアライズ可能なデータを渡します
制約と警告
- サーバーコンポーネント:フック、イベントハンドラー、ブラウザー API は使用できません
- use() フック:レンダー中にのみ呼び出せます。コールバックやエフェクト内では呼び出せません
- サーバーアクション:
'use server'ディレクティブを含める必要があります。必ず入力を検証してください - 状態の変更:状態を直接変更しないでください。必ず新しい参照を作成してください
- エフェクトの依存関係:
useEffectの依存配列にすべての依存関係を含めてください - メモリーリーク:
useEffectの戻り値で、サブスクリプションとイベントリスナーを必ずクリーンアップしてください
参考資料
詳しいパターンについては、以下のファイルを参照してください。
- references/hooks-patterns.md — useState、useEffect、useRef、useReducer、カスタムフック、よくある落とし穴
- references/component-patterns.md — Props、コンポジション、状態のリフトアップ、コンテキスト、複合コンポーネント、エラーバウンダリー
- references/react19-features.md — use()、useOptimistic、useFormStatus、useActionState、サーバーアクション、サーバーコンポーネント、移行ガイド
- references/performance-patterns.md — React Compiler のセットアップ、useMemo、useCallback、useTransition、useDeferredValue、遅延読み込み
- references/typescript-patterns.md — 型付き props、ジェネリックコンポーネント、イベントハンドラー、判別可能なユニオン、コンテキストの型指定
- references/learn.md — 基礎から React 19 の高度な内容までを段階的に学ぶガイド
- references/reference.md — すべての React フックとコンポーネント API の完全な API リファレンス
コメント
Markdown 対応