Custom Hookは重複排除だけで作らない
React Custom Hook 設計で失敗しやすいのは、似たコードを見つけた瞬間にHookへ切り出してしまうことです。
結論から言うと、Custom Hookは重複排除の道具だけではありません。状態、イベント、副作用、外部APIとの同期を、UIから分離して名前を付けるための設計単位です。
ただし、切り出した結果、引数が増え、戻り値が肥大化し、useEffectの依存が読めなくなるなら逆効果です。そのため、実務では「何を隠し、何を公開するか」を先に決めます。
React Custom Hook設計で切り出す判断基準
| 切り出す対象 | 向いている度合い | 理由 |
|---|---|---|
| API取得と読み込み状態 | 高い | 状態と副作用がまとまりやすい |
| フォーム入力の補助 | 高い | 変更処理と検証をまとめやすい |
| 単純な表示条件 | 低い | コンポーネント内で読める方がよい |
| 複数画面で使う業務ロジック | 高い | テスト対象として切り出しやすい |
| 一度しか使わない巨大処理 | 中程度 | 名前で責務が明確になるなら有効 |
例えば、React公式のReusing Logic with Custom Hooksでも、Custom Hookはロジックを再利用する手段として説明されています。ただし、実務では再利用よりも責務分離が効く場面も多いです。
失敗例:何でも返すuseUserは読みにくい
まず、次のようなHookは最初は便利です。しかし、画面が増えるほど責務が膨らみます。
function useUser(userId: string) {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(false);
const [editing, setEditing] = useState(false);
const [message, setMessage] = useState("");
useEffect(() => {
setLoading(true);
fetch(`/api/users/${userId}`)
.then((res) => res.json())
.then(setUser)
.finally(() => setLoading(false));
}, [userId]);
return { user, loading, editing, setEditing, message, setMessage };
}
つまり、取得、編集状態、メッセージが混ざっています。その結果、このHookを修正するとき、どの画面に影響するか読みづらくなります。
改善例:責務でHookを分ける
function useUserQuery(userId: string) {
const [state, setState] = useState<{
user: User | null;
loading: boolean;
}>({ user: null, loading: false });
useEffect(() => {
let ignore = false;
setState((current) => ({ ...current, loading: true }));
fetch(`/api/users/${userId}`)
.then((res) => res.json())
.then((user) => {
if (!ignore) setState({ user, loading: false });
});
return () => {
ignore = true;
};
}, [userId]);
return state;
}
一方で、このHookはユーザー取得に集中しています。そのため、編集状態は別のHookかコンポーネントへ置けます。なお、useEffectの基本はReact公式のuseEffectリファレンスも参照するとよいです。
状態管理とCustom Hookの境界を決める
ただし、Custom Hookは状態管理ライブラリの代替ではありません。そこで、画面ローカルの状態を扱うのか、アプリ全体の状態を扱うのかを分けます。
- 画面内で閉じる状態はCustom Hookでよい
- 複数画面で共有する状態はContextや状態管理ライブラリを検討する
- サーバーデータはキャッシュ戦略も含めて設計する
- Hook名は `useUserQuery` や `useFormDraft` のように責務を表す
さらに、レビューではHookの戻り値が増えすぎていないかを見ます。戻り値が多い場合、そのHookは複数の責務を抱えている可能性があります。
テストしやすいCustom Hook設計にする
また、Custom Hookを作るなら、テストしやすさも確認します。入力、状態遷移、外部APIへの依存が見えると、テストの粒度を決めやすくなります。
- Hook内部で直接windowやlocalStorageへ依存しすぎない
- API関数を引数やモジュール境界で差し替えられるようにする
- 非同期処理はloading、error、dataの状態を明確にする
- UI文言や表示レイアウトはHookへ入れない
例えば、ReactのテストではReact Testing Libraryを使う場面が多いです。一方で、Hook単体で見るか、利用コンポーネント越しに見るかは、責務の大きさで判断します。
レビューで確認したいチェックリスト
- Hook名が責務を説明しているか
- UIの都合と業務ロジックが混ざっていないか
- useEffectの依存配列が自然に読めるか
- 戻り値が多すぎないか
- 同じHookを使う画面同士が不要に結合していないか
- テストで状態遷移を確認できるか
まとめ:Custom Hookは責務を名前で固定する
最後に、React Custom Hook 設計では、切り出すこと自体を目的にしないことが重要です。UIから隠したい状態と副作用を決め、Hook名で責務を表します。
その結果、よいCustom Hookはコンポーネントを薄くします。同時に、レビューとテストの対象を明確にします。
React/TypeScript案件では、Hook設計、状態管理、useEffectの扱いが保守性に直結します。今の経験を活かせる現場や、レビュー文化のある案件を相談したい方は、技術テーマを含めて一度話してみてください。
一度カジュアル面談をしませんか?
株式会社bluenaは「高還元」と「伴走支援」を両立したSES企業です。単価の81〜86%を還元する報酬体系と、専任サポーターによる隔週1on1で、エンジニアが納得できるキャリアを実現します。
まとまっていなくてもOK——まずは現在地を聞かせてください。
カジュアル面談ですので、お気軽にお聞かせください。





