react-advanced について
TanStack エコシステムと XState を使うモダンな React アプリ向けの、クロスプラットフォームな規約と設計ガイドです。React Web と React Native の開発者を対象に、サーバーデータ、UI 状態、フォーム、非同期処理の役割分担やよくある問題を説明します。
主な機能
- 01サーバー状態とクライアント状態の分離
- 02用途別のライブラリ責務の定義
- 03XState を使う判断基準の提示
- 04コンポーネント合成パターンの説明
- 05テーマ別リファレンスファイルの提供
活用シーン
- 01React アプリの状態管理の設計
- 02複数段階の非同期フローの設計
- 03useEffect での派生状態処理の回避
- 04フォームとサーバーデータの管理方法の選択
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ trancong12102/ agentskills --skill react-advancedインストール後、Claude Code で/react-advancedを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/form.mdintegration.mdreact-query.mdtable.mdtesting.mdxstate.mdzod.mdzustand.md詳細
React Advanced:コアパターンと規約(クロスプラットフォーム)
このスキルでは、TanStack エコシステムと XState を使用して最新の React アプリケーションを構築する際のルール、規約、アーキテクチャ上の決定を定めます。よくある落とし穴を防ぎ、拡張性のあるパターンを徹底するため、意図的に明確な方針を採用しています。
これらのパターンは、Web と React Native のどちらでも同じように機能します。プラットフォーム固有のパターンについては、以下を参照してください。
- Web:
react-web-advancedを参照してください(TanStack Router、Start、Virtual) - React Native:
react-native-advancedを参照してください(Expo Router、FlashList、MMKV)
ここで言及するライブラリの詳しい API ドキュメントについては、適切な他のツール (ドキュメントの検索、ウェブ検索など)を使用してください。このスキルでは、これらのツールの完全な API 仕様ではなく、使い方と使う理由に焦点を当てています。
useEffect の禁止
以下の用途に useEffect を使用しないでください。
- データ取得 — React Query(
useSuspenseQuery/useQuery)またはルートローダーを使用してください - 派生状態 — レンダリング中に計算するか、
useMemoを使用してください - props との状態の同期 — prop を直接使用するか、React の
keyを使ってリセットしてください - ユーザーイベントへの応答 — イベントハンドラーにロジックを記述してください
- 外部ストアの購読 —
useSyncExternalStoreを使用してください - 複雑な非同期処理 —
invoke/fromPromiseを使用する XState マシンを使ってください
useEffect の許容される用途
正当なケース:
- React 以外の外部システムとの同期 — DOM API、サードパーティ製ウィジェット (マップ、チャート)、マウント/アンマウントのライフサイクルが必要な命令型ライブラリ
- クリーンアップを伴うブラウザー/ネイティブ API の購読 — 単発のケースで
useSyncExternalStoreの抽象度が低すぎる場合(WebSocket 接続、リサイズオブザーバー) - マウント時の分析/ログ記録 — 状態を更新しない、実行してそのまま完了する副作用
- React Query のデータを XState に橋渡しする — イベントを介してサーバー状態をマシンに
送るための
useEffectブリッジパターン(references/xstate.mdを参照)
マウント時のみの副作用に使う useMountEffect
正当なマウント時のみの副作用(上記のケース 1〜3)がある場合は、生の useEffect(fn, []) の代わりに
useMountEffect ヘルパーを使用してください。
// utils/useMountEffect.ts
import { useEffect, type EffectCallback } from "react";
// eslint-disable-next-line react-hooks/exhaustive-deps
const useMountEffect = (effect: EffectCallback) => useEffect(effect, []);
export default useMountEffect;
使用例:
useMountEffect(() => {
const plugin = $.myPlugin(ref.current);
return () => {
plugin.destroy();
};
});
理由:生の useEffect(fn, []) は react-hooks/exhaustive-deps リントルールに抵触し、
空の配列が意図的であることを開発者に証明させます。useMountEffect を使えば、「マウント時に一度だけ実行する」という意図がコード上で明確になり、警告も正しく抑制されます。
代わりに使うもの
| useEffect の代わりに使う用途 | 使用するもの |
|---|---|
| データ取得 | useSuspenseQuery + ルートローダーでの先行取得 |
| Promise の処理 | use() フック(React 19 以降)+ <Suspense> |
| 派生値/計算値 | 直接計算または useMemo |
| 外部ストアの購読 | useSyncExternalStore |
| 負荷の高いレンダリングの遅延 | useDeferredValue / useTransition |
| 複雑な非同期オーケストレーション | fromPromise を使う XState の invoke |
| prop の変更時に状態をリセット | コンポーネントの React key prop |
| ユーザーが起動する副作用 | イベントハンドラーに直接記述 |
状態管理の考え方
サーバー状態とクライアント状態 — 決して混在させない
サーバー状態(API/データベースから取得するデータ)とクライアント状態(クライアント内にのみ存在する UI 状態)は、根本的に異なる関心事です。混在させると、データの陳腐化によるバグ、重複、同期に関する深刻な問題が発生します。
| 関心事 | 所有者 | 例 |
|---|---|---|
| サーバーデータ | React Query | ユーザー、投稿、製品、注文 |
| URL/ルート状態 | Router | パスパラメーター、検索パラメーター(TanStack Router または Expo Router) |
| 複雑な UI フロー | XState | 複数ステップのウィザード、認証フロー、ドラッグ&ドロップ |
| 共有クライアント UI | Zustand | テーマ、サイドバー、選択項目、グローバルフィルター、設定 |
| フォームフィールド | TanStack Form | 入力値、検証エラー、送信 |
| スキーマ検証 | Zod | 検索パラメーター、フォームバリデーター、API コントラクト |
| シンプルなローカル UI | useState | トグル、アコーディオンの展開状態、入力フォーカス |
判断フローチャート
Is the data from a server / API?
YES -> React Query (queryOptions + useSuspenseQuery)
NO -> Is it in the URL / route params?
YES -> Router (platform-specific: TanStack Router or Expo Router)
NO -> Is it a complex multi-state flow (3+ states, async, guards)?
YES -> XState (useMachine / createActorContext)
NO -> Is it a form field?
YES -> TanStack Form (with Zod validators)
NO -> Is it shared across components / trees?
YES -> Zustand (create store + selectors)
NO -> useState / useReducer
useState/useReducer より XState を選ぶ場合
次のような場合は XState を使用します。
- 遷移が定義された、3 つ以上の相互排他的な状態がある
- 非同期の副作用を状態変更時にキャンセルする必要がある(競合状態)
- ロジックに、遷移を制御する条件であるガードがある
- 独立した関心事に対して並列状態が必要である
- React から分離してフローをテストする必要がある
- 戻る/進むナビゲーションを含む複数ステップのフローがある(ウィザード)
シンプルなトグル、単一のブール値フラグ、カウンター状態には XState を使わないでください。これらには useState を使用します。
アーキテクチャ:各ライブラリの役割
| レイヤー | ライブラリ | 責務 |
|---|---|---|
| サーバー状態 | React Query | 取得、キャッシュ、無効化、バックグラウンドでの再取得 |
| 複雑な UI 状態 | XState | ステートマシン、アクターモデル、フローのオーケストレーション |
| 共有クライアント UI | Zustand | コンポーネント間で共有する UI 状態、設定、選択項目 |
| フォームのライフサイクル | TanStack Form | フィールド値、検証、送信 |
| スキーマ検証 | Zod | 検索パラメーター、フォームバリデーター、API コントラクト |
| データ表示 | TanStack Table | ヘッドレスな並べ替え、フィルタリング、ページネーション、グループ化 |
| テスト | Vitest + TL | ユニット、コンポーネント、統合、マシンのテスト |
| シンプルなローカル状態 | useState | トグル、ローカル入力、コンポーネント内の値 |
黄金律:queryOptions を信頼できる唯一の情報源にする
クエリオプションは一度だけ定義し、ローダー、コンポーネント、無効化処理など、あらゆる場所でインポートします。
export const postsQueryOptions = queryOptions({
queryKey: ["posts"],
queryFn: fetchPosts,
staleTime: 30_000,
});
export const postQueryOptions = (postId: string) =>
queryOptions({
queryKey: ["posts", postId],
queryFn: () => fetchPost(postId),
staleTime: 30_000,
});
コンポーネントの合成
複合コンポーネント
コンポーネントのグループが暗黙的な状態を共有する場合は、Context ベースの複合コンポーネントを使用します。親が状態を管理し、子はコンテキストを通じて状態を利用します。コンテキストの値はメモ化してください。
スロットパターン
スロットのような合成には、名前付きプロパティ(header、footer、actions)を使用します。深くネストしたレンダープロップのツリーは避けてください。
制御の反転
呼び出し元固有の動作に対応するためにブール値のプロパティや分岐ロジックを追加する場合は、コールバック、リデューサー、またはレンダー関数を通じて、そのロジックを呼び出し元に戻してください。似たような if 文が 3 つある場合は、制御を反転する合図です。
よくある落とし穴
-
useEffect で派生状態を扱う — エフェクト内で値を計算して useState に保存すると、 再レンダリングが二重に発生します。レンダー中に計算するか、
useMemoを使用してください。 -
サーバーデータをクライアント状態に保存する — API レスポンスを Zustand/Redux に格納すると、 キャッシュと無効化を自分で管理することになります。代わりに React Query を使用してください。
-
URL 状態を useState に重複して保存する — ルーターの検索パラメーター/パラメーターフックを直接使用してください。
-
同じデータに React Query と XState の両方を使用する — React Query がデータの取得とキャッシュを管理します。 XState はイベント経由でデータを受け取り、オーケストレーションのみを処理します。
-
XState マシン内で React フックを呼び出す — フックは React コンポーネント内でのみ使用できます。 マシン内では
fromPromiseを使用し、useEffectのイベント経由でデータを連携してください。 -
配列のインデックスをキーとして使う — 安定した ID(
item.id)を使用してください。インデックスをキーにすると、並べ替え/挿入/削除時に状態の関連付けが誤ります。 -
コンポーネント内でコンポーネントを定義する — レンダーのたびに新しいコンポーネント型が作成され、 React にアンマウントと再マウントを強制します。モジュールレベルで定義してください。
-
更新頻度の高い状態に Context を使う — Context は変更のたびにすべてのコンシューマーを再レンダリングします。 共有され、頻繁に変化する値にはセレクターを使った Zustand を使用するか(
references/zustand.mdを参照)、 コンポーネント内に限定される状態にはローカル状態を使用してください。 -
Zod の検索パラメータースキーマで
.catch()を使わない —.default()が処理するのは キーがない場合のみです。.catch()は不正な URL による無効な値も処理します。
リファレンスファイル
特定のライブラリを扱う際は、関連するリファレンスファイルを読んでください。
| ファイル | 読むタイミング |
|---|---|
references/react-query.md | クエリのパターン、ミューテーション、キャッシュ、Suspense との連携 |
references/table.md | 列定義、並べ替え、フィルタリング、ページネーション、サーバーサイド処理 |
references/form.md | フィールドの検証、配列、スキーマ検証、パフォーマンス |
references/xstate.md | ステートマシン、アクター、認証フロー、ウィザード、React との連携 |
references/zustand.md | 共有クライアント UI 状態、セレクター、スライス、ミドルウェア、バニラストア |
references/zod.md | スキーマ検証、v4 API、Form との連携、エラー処理 |
references/testing.md | Vitest のセットアップ、Testing Library、MSW、Query/Form/XState のテスト |
references/integration.md | ライブラリの組み合わせ:Zustand+XState、Query+XState のブリッジ |