ideal-react-component について
React 開発者向けに、関数コンポーネントを7つのセクションに整理する方法と、よくある Hooks のアンチパターンを解説するスキルです。状態ロジックをカスタムフックへ切り出す目安も示します。
主な機能
- 01依存順に沿った7セクション構成
- 02よくある Hooks のアンチパターン
- 03コンポーネントのリファクタリングとフック抽出
- 04早期 return とフック呼び出し順の説明
- 05必要に応じて参照できる詳細資料
活用シーン
- 01React 関数コンポーネントの構成統一
- 02再レンダーや古い状態の問題調査
- 03複雑なロジックのカスタムフックへの抽出
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ antjanus/ skillbox --skill ideal-react-componentインストール後、Claude Code で/ideal-react-componentを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
reference/COMPLETE-EXAMPLES.mdEVAL.mdHOOKS-ANTIPATTERNS.mdREFACTORING.mdSECTIONS.md詳細
理想的な React コンポーネント構造
概要
関数コンポーネントのファイルを、インポート → スタイル → 型 → コンポーネント → ロジック → 条件付きレンダリング → デフォルトレンダリングの、予測しやすい7つのセクションに並べます。これにより、ファイルを開いた人がどこを見ればよいかすぐにわかります。また、無限ループや古い状態の主な原因となる、フックのアンチパターンも取り上げます。
基本原則: ファイルは依存関係の順に上から下へ読み進められ、ハッピーパスは最も目につく最下部に配置します。これはパターンであり、絶対的な規則ではありません。小さなコンポーネントや React Server Components では、該当するセクションを省略しても問題ありません。
7つのセクション構造
// 1. IMPORTS (grouped: React → third-party → internal @/ → local, blank line between)
import React, { useState, useEffect } from 'react';
import { useQuery } from '@tanstack/react-query';
import styled from 'styled-components';
import { api } from '@/services/api';
import { Button } from './Button';
// 2. STYLED COMPONENTS (prefix "Styled" so they're instantly recognizable)
const StyledContainer = styled.div`padding: 1rem; background: white;`;
// 3. TYPE DEFINITIONS (ComponentNameProps, declared right above the component)
type UserProfileProps = {
userId: string;
onUpdate?: (user: User) => void;
};
// 4. COMPONENT FUNCTION (named export, const arrow function)
export const UserProfile = ({ userId, onUpdate }: UserProfileProps): React.JSX.Element => {
// 5. LOGIC
const [isEditing, setIsEditing] = useState(false); // 5.1 local state
const { data, isLoading, error } = useQuery({ queryKey: ['user', userId], queryFn: () => api.getUser(userId) }); // 5.2 custom/data hooks
useEffect(() => { /* focus the editor */ }, [isEditing]); // 5.3 effects
const displayName = data ? `${data.first} ${data.last}` : ''; // 5.4 post-processing
const handleEdit = () => setIsEditing(true); // 5.5 handlers
// 6. CONDITIONAL RENDERING (exit early for each edge case, after every hook call)
if (isLoading) return <Loading />;
if (error) return <Error message={error.message} />;
if (!data) return <Empty />;
// 7. DEFAULT RENDER (success/happy path stays at the bottom — most visible)
return <StyledContainer>{/* Main JSX */}</StyledContainer>;
};
状態を最初に、依存するフックの後にエフェクトを、最後にハンドラーを配置します。これにより、各行はそれより上で宣言されたものだけを参照します。
JavaScript: 型注釈なしで同じパターンを使います。セクション3を省略するか、JSDoc を使ってください。
| セクション | 内容 | 理由 |
|---|---|---|
| 1. インポート | React、ライブラリ、内部の @/、ローカルのもの | 依存関係を見つけやすくします |
| 2. スタイル | Styled コンポーネント(接頭辞は Styled)、Tailwind、CSS Modules | ロジックと視覚的に分離できます |
| 3. 型 | コンポーネントの上に *Props、*Return | APIをひと目で確認できます |
| 4. コンポーネント | export const Component = (...) => | 名前付きエクスポートはリファクタリングしやすくなります |
| 5. ロジック | 状態 → フック → エフェクト → 後処理 → ハンドラー | フックのルールに従い、依存元を依存先より先に配置できます |
| 6. 条件付きレンダリング | 読み込み中/エラー/空の状態に対する早期リターン | ネストを減らし、ガードの後で型を絞り込めます |
| 7. デフォルトレンダリング | 成功状態の JSX | ハッピーパスが最も目につくコードになります |
よくあるフックのアンチパターン
無限ループ、古いデータ、予期しない再レンダリングの最も一般的な原因を3つ紹介します。まず望ましい形式を示します。
1. レンダリング中に値を導出し、エフェクトで同期しない。 他の状態から状態を計算するだけのエフェクトは、余分なレンダーパスを発生させます。また、親がその値を再び渡すと、無限ループが発生します。
const fullName = `${first} ${last}`; // ✅ derive during render
useEffect(() => { setFullName(`${first} ${last}`); }, [first, last]); // ❌ extra render, loop risk
2. key でプロパティ由来の状態をリセットし、useState がプロパティを追跡することを期待しない。 初期化関数は1回しか実行されないため、プロパティが変わっても状態は古いままです。
<Component key={itemId} initialValue={data.value} /> // ✅ remount resets state
const [value, setValue] = useState(props.initialValue); // ❌ stale after prop changes
3. 依存関係をすべて列挙する。 依存関係を省略すると、古いクロージャーが取り込まれ、エフェクトは最初のレンダリング時の値を読み続けます。
useEffect(() => { setTotal(count * price); }, [count, price]); // ✅ all deps
useEffect(() => { setTotal(count * price); }, [price]); // ❌ missing count
リファクタリング
コンポーネントのロジックが約50行、または全体が約200行を超えたら、状態を持つロジックを use[Domain] フックに切り出します。これにより、コンポーネントは表示に専念し、フックが状態とデータフローを担います。リファクタリングするのは、ユーザーが指定したコンポーネントです。同じ問題を抱える兄弟コンポーネントや親コンポーネントは、同じ変更で手を加えるファイルではなく、後続対応として報告してください。切り出しの基準と合成パターンについては、reference/REFACTORING.md を参照してください。
注意点
- 早期リターンは、すべてのフック呼び出しの後に置く必要があります。 Section 6がSection 5の後にあるのは、単なる整頓上の理由ではありません。上にある
returnによってレンダー間でフックの数が変わると、Reactは「Rendered fewer hooks than expected.」というエラーをスローします。ガードで高コストな処理を短絡させたい場合は、フックの内部で条件分岐してください。 - Server ComponentsではSection 2と5を使用できません。 Next.js App Routerでは、ファイルはデフォルトでServer Componentです。
useState、useEffect、styled-componentsを使うには、ファイルの先頭に'use client'が必要です。ビルドエラーにはフックの名前が示されますが、ディレクティブがないことは示されません。 - 依存配列内のオブジェクトや配列リテラルは、無限ループを引き起こします。
}, [{ id }])や}, [items.filter(...)])はレンダーごとに新しい参照を生成するため、エフェクトが毎回再実行されます。リテラルをエフェクト内に移動するか、useMemoでメモ化してください。 keyによるリセットは、サブツリー全体を再マウントします。 子の状態、非制御入力の値、フォーカス、実行中のアニメーションが失われます。「これは別のレコードになった」という場合には適切ですが、値を再設定する必要があるフィールドが1つだけの場合には適しません。- 計測して必要だと分かるまでは、メモ化より導出を優先してください。
useMemoを使うのは、計算が本当に高コストな場合に限ってください。テンプレート文字列や短いリストに対する.map()は、メモ化の管理処理よりも低コストです。 - 戻り値の型は、単なる
JSX.ElementではなくReact.JSX.Elementです。 React 19の型定義ではグローバルなJSX名前空間が削除されたため、修飾子のない形式は解決できなくなりました。 @tanstack/react-queryv5では、引数は1つのオブジェクトです。 位置引数を使うuseQuery(key, fn, options)のオーバーロードは削除されました。また、v5ではステータスの'loading'が'pending'に改名され、isLoadingはisPending && isFetchingとして再定義されました。そのため、「まだデータがない」状態はisPendingで表します。- GSDの実行フェーズでは、スキルは自動で有効になりません。
/ideal-react-componentを明示的に呼び出すか、プロジェクトのCLAUDE.mdに規約を記録してください。
詳細リファレンス
質問に応じてファイルを1つ読み込んでください。最初から読み込む必要はありません。
| 読み込むファイル | 用途 |
|---|---|
| reference/SECTIONS.md | セクションごとの詳細な✅/❌、スタイリング方法のバリエーション、構造上のトラブルシューティング |
| reference/HOOKS-ANTIPATTERNS.md | 仕組みを含むアンチパターン全般。上記の代表的な3つは要約です |
| reference/REFACTORING.md | カスタムフックの抽出と組み合わせ |
| reference/COMPLETE-EXAMPLES.md | 7つのセクションをすべて含む、TSとJSの完全なコンポーネント例 |
| reference/EVAL.md | 有効化評価セット(メンテナー専用) |