はじめに
Reactを書いていると「このHook、どう使うんだっけ?」となる瞬間、ありませんか?
本記事はそんな時にサッと開いて確認できるチートシートとして、React 19時点のHooksを網羅的にまとめたものです。
各Hookは 「一言で言うと」→「シグネチャ」→「最小コード例」→「落とし穴」 の順で整理しています。必要な場所にジャンプして使ってください。
対象バージョン: React 19(React 18以前での差分は都度補足します)
Hooksの基本ルール(最初に必ず押さえる)
React公式が定める 「Rules of Hooks」 です。違反するとバグるか、ESLintに怒られます。
-
トップレベルでのみ呼ぶ —
if・for・ネストした関数の中で呼ばない - React関数の中でのみ呼ぶ — コンポーネントかカスタムHookの中だけ
-
名前は
useで始める — Reactがフックだと認識するため
// ❌ 悪い例:条件分岐の中で呼んでいる
function Bad({ flag }) {
if (flag) {
const [count, setCount] = useState(0); // NG
}
}
// ✅ 良い例:常にトップレベル
function Good({ flag }) {
const [count, setCount] = useState(0);
if (flag) { /* ... */ }
}
Reactは 「呼び出し順序」 でHookを識別しています。条件分岐の中で呼ぶと順序が崩れ、stateが別のHookに紐付く重大バグの原因になります。
eslint-plugin-react-hooks を導入しておけば自動で検知できるので必ず入れましょう。
🔵 State管理系
useState — ローカルstateの基本
一言で言うと: コンポーネントが値を記憶するための最も基本的なHook
const [state, setState] = useState(initialState);
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
{count}
</button>
);
}
関数型更新(前のstateに依存する場合は必ずこちら):
setCount(prev => prev + 1); // ✅ 直前のstateを確実に参照
遅延初期化(重い処理を初回だけ実行):
const [data, setData] = useState(() => expensiveComputation());
setStateを呼ぶたびに再レンダリングが発生します。同じ値をセットするとReactが最適化してスキップしますが、オブジェクトや配列は参照比較なのでミューテートせず新しいオブジェクトを作りましょう。
useReducer — 複雑なstateロジック向け
一言で言うと: Reduxっぽく、actionでstateを更新するHook
const [state, dispatch] = useReducer(reducer, initialState);
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment': return { count: state.count + 1 };
case 'decrement': return { count: state.count - 1 };
case 'reset': return initialState;
default: throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
<p>{state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
</>
);
}
useState と useReducer の使い分け:
| ケース | 推奨 |
|---|---|
| 単純な値(真偽値・数値・文字列) | useState |
| 複数の値が連動して変わる | useReducer |
| stateの更新ロジックが複雑 | useReducer |
| 次のstateが複数の値から決まる | useReducer |
🟢 副作用・ライフサイクル系
useEffect — 副作用の実行
一言で言うと: レンダリング後にDOM操作・API通信・購読などを行うHook
useEffect(setup, dependencies?);
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
let cancelled = false;
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => {
if (!cancelled) setUser(data);
});
return () => { cancelled = true; }; // クリーンアップ
}, [userId]); // userIdが変わったら再実行
return user ? <p>{user.name}</p> : <p>Loading...</p>;
}
依存配列の3パターン:
| 書き方 | 実行タイミング |
|---|---|
useEffect(fn) |
毎レンダリング後 |
useEffect(fn, []) |
マウント時のみ(1回だけ) |
useEffect(fn, [dep]) |
depが変わった時 |
依存配列の省略はバグの温床です。eslint-plugin-react-hooksのexhaustive-depsルールを必ず有効にしましょう。関数を依存に入れたくない時はuseCallbackか、関数の中にロジックを移動します。
React 18のStrictModeでは開発環境でEffectが 2回実行 されます。クリーンアップ関数が正しく書けているかチェックするための挙動なので、本番では1回です。
useLayoutEffect — 描画前の同期的な副作用
一言で言うと: DOMに反映される前に同期的に走るuseEffect
useLayoutEffect(() => {
// DOMのサイズを測って、画面に見える前に調整するなど
const { width } = ref.current.getBoundingClientRect();
setWidth(width);
}, []);
useEffect との違い:
-
useEffect: ブラウザ描画後に非同期実行(基本こっちでOK) -
useLayoutEffect: ブラウザ描画前に同期実行(ちらつき防止が必要な時だけ)
useLayoutEffectはパフォーマンスに影響するので、本当に必要な時だけ使いましょう。
useInsertionEffect — CSS-in-JSライブラリ向け
一言で言うと: DOMのmutation前に走る、主にCSS-in-JS作者向けの特殊Hook
通常のアプリ開発ではまず使いません。styled-componentsやemotionのようなライブラリの内部実装用です。
🟡 パフォーマンス最適化系
useMemo — 計算結果のメモ化
一言で言うと: 重い計算を依存配列が変わった時だけ再実行する
const memoizedValue = useMemo(() => computeExpensive(a, b), [a, b]);
function SearchResults({ items, query }) {
const filtered = useMemo(
() => items.filter(item => item.name.includes(query)),
[items, query]
);
return <List items={filtered} />;
}
多用は禁物です。useMemo自体にもコストがあるので、計測して本当に重い処理だけに使いましょう。
useCallback — 関数のメモ化
一言で言うと: 関数の参照を固定して、子コンポーネントの不要な再レンダリングを防ぐ
const memoizedFn = useCallback(() => { doSomething(a, b); }, [a, b]);
function Parent() {
const [count, setCount] = useState(0);
// countが変わっても関数参照は同じ
const handleClick = useCallback(() => {
console.log('clicked');
}, []);
return <MemoizedChild onClick={handleClick} />;
}
useCallback(fn, deps) は useMemo(() => fn, deps) とほぼ等価です。
React.memoでラップした子コンポーネントに渡す関数以外、useCallbackはむしろ害になることが多いです。「とりあえず全部useCallback」はアンチパターン。
🟣 Ref・DOM操作系
useRef — 再レンダリングを起こさない値の保持
一言で言うと: 値を保持するが、変更しても再レンダリングしないHook
用途は大きく2つ:
① DOM要素への参照:
function TextInput() {
const inputRef = useRef(null);
const focus = () => inputRef.current.focus();
return (
<>
<input ref={inputRef} />
<button onClick={focus}>フォーカス</button>
</>
);
}
② レンダリング間で値を保持(タイマーIDや前回の値など):
const timerRef = useRef(null);
useEffect(() => {
timerRef.current = setInterval(() => { /* ... */ }, 1000);
return () => clearInterval(timerRef.current);
}, []);
ref.currentを レンダリング中に読み書きしないでください。副作用やイベントハンドラの中でのみ操作します。
useImperativeHandle — 親に公開するrefをカスタマイズ
一言で言うと: 子コンポーネントが親に公開するメソッドを絞り込む
import { forwardRef, useImperativeHandle, useRef } from 'react';
const FancyInput = forwardRef((props, ref) => {
const inputRef = useRef(null);
useImperativeHandle(ref, () => ({
focus: () => inputRef.current.focus(),
clear: () => { inputRef.current.value = ''; },
}));
return <input ref={inputRef} />;
});
// 親側
const ref = useRef();
<FancyInput ref={ref} />
ref.current.focus(); // 公開したメソッドだけ呼べる
React 19からforwardRefなしでもrefをpropsとして受け取れるようになりました。
🟠 Context・共有state系
useContext — Contextの値を読む
一言で言うと: Propsのバケツリレーを避けて、階層をまたいで値を共有する
const ThemeContext = createContext('light');
function App() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar() {
const theme = useContext(ThemeContext);
return <div className={theme}>...</div>;
}
Contextの値が変わると、それを読んでいる全コンポーネントが再レンダリングされます。頻繁に変わる値をContextに入れるとパフォーマンス問題の原因になります。
🔴 並行レンダリング系(React 18+)
useTransition — 重い更新を「遷移」として扱う
一言で言うと: UIの応答性を保ったまま、重たいstate更新を後回しにする
const [isPending, startTransition] = useTransition();
function SearchBox() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
const handleChange = (e) => {
setQuery(e.target.value); // 緊急性高:即反映
startTransition(() => {
setResults(heavySearch(e.target.value)); // 遷移:中断OK
});
};
return (
<>
<input value={query} onChange={handleChange} />
{isPending && <Spinner />}
<Results items={results} />
</>
);
}
useDeferredValue — 値の更新を遅延させる
一言で言うと: 値が落ち着くまで古い値を使い続ける
const deferredQuery = useDeferredValue(query);
function Search({ query }) {
const deferredQuery = useDeferredValue(query);
const results = useMemo(
() => expensiveSearch(deferredQuery),
[deferredQuery]
);
return <Results items={results} />;
}
useTransition はstate更新側で使い、useDeferredValue は受け取った値側で使います。
useId — ユニークなIDを生成
一言で言うと: SSR対応のユニークID生成Hook
function FormField() {
const id = useId();
return (
<>
<label htmlFor={id}>名前</label>
<input id={id} />
</>
);
}
リストのkeyには使わないでください。keyにはデータ固有のIDを使います。
useSyncExternalStore — 外部ストアの購読
一言で言うと: ReduxやZustandなど、React外のストアを安全に購読する
const state = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?);
function useOnlineStatus() {
return useSyncExternalStore(
(callback) => {
window.addEventListener('online', callback);
window.addEventListener('offline', callback);
return () => {
window.removeEventListener('online', callback);
window.removeEventListener('offline', callback);
};
},
() => navigator.onLine,
() => true // SSR時の値
);
}
通常のアプリ開発で直接使うことは少なく、ライブラリ作者向けのHookです。
🆕 React 19の新Hooks
use — PromiseやContextを読む新API
一言で言うと: Suspenseと組み合わせてPromiseの値を同期的に読める特殊なAPI
import { use } from 'react';
function Comments({ commentsPromise }) {
const comments = use(commentsPromise); // Suspenseで待つ
return comments.map(c => <p key={c.id}>{c.text}</p>);
}
function Page() {
const commentsPromise = fetchComments();
return (
<Suspense fallback={<Loading />}>
<Comments commentsPromise={commentsPromise} />
</Suspense>
);
}
useが他のHookと違う点:
- 条件分岐の中で呼んでOK(唯一の例外)
- Promiseだけでなく、
use(MyContext)のようにContextも読める
useActionState — フォームaction + state管理
一言で言うと: フォームのサーバーアクションと一緒にstateとpending状態を管理する
const [state, formAction, isPending] = useActionState(action, initialState);
async function submitForm(prevState, formData) {
const name = formData.get('name');
if (!name) return { error: '必須です' };
await saveToServer(name);
return { success: true };
}
function MyForm() {
const [state, formAction, isPending] = useActionState(submitForm, {});
return (
<form action={formAction}>
<input name="name" />
<button disabled={isPending}>送信</button>
{state.error && <p>{state.error}</p>}
</form>
);
}
useFormStatus — 親フォームの送信状態を取得
一言で言うと: <form>の子コンポーネントから送信中かどうか知れる
import { useFormStatus } from 'react-dom';
function SubmitButton() {
const { pending } = useFormStatus();
return <button disabled={pending}>{pending ? '送信中...' : '送信'}</button>;
}
function MyForm() {
return (
<form action={myAction}>
<input name="email" />
<SubmitButton /> {/* pendingを自前で受け取らなくてよい */}
</form>
);
}
react-domからインポートする点に注意。reactではありません。
useOptimistic — 楽観的UI更新
一言で言うと: サーバー応答を待たず、先にUIを更新してユーザー体験を高速化
const [optimisticState, addOptimistic] = useOptimistic(state, updateFn);
function Todos({ todos, addTodo }) {
const [optimisticTodos, addOptimisticTodo] = useOptimistic(
todos,
(current, newTodo) => [...current, { ...newTodo, pending: true }]
);
async function formAction(formData) {
const text = formData.get('text');
addOptimisticTodo({ text }); // 即座にUI更新
await addTodo(text); // 実際のサーバー処理
}
return (
<>
{optimisticTodos.map(t => (
<li key={t.id} style={{ opacity: t.pending ? 0.5 : 1 }}>{t.text}</li>
))}
<form action={formAction}>
<input name="text" />
</form>
</>
);
}
🛠️ カスタムHook — 自作Hookの作り方
ロジックを再利用したい時は、useで始まる関数として切り出すだけでカスタムHookになります。
// カスタムHook
function useWindowSize() {
const [size, setSize] = useState({
width: window.innerWidth,
height: window.innerHeight,
});
useEffect(() => {
const handler = () => setSize({
width: window.innerWidth,
height: window.innerHeight,
});
window.addEventListener('resize', handler);
return () => window.removeEventListener('resize', handler);
}, []);
return size;
}
// 使用側
function App() {
const { width, height } = useWindowSize();
return <p>{width} x {height}</p>;
}
よく作るカスタムHook例:
-
useLocalStorage— localStorage連携state -
useDebounce— 値のデバウンス -
usePrevious— 前回の値を取得 -
useFetch— APIフェッチのラッパー
早見表
| Hook | 用途 | 追加時期 |
|---|---|---|
useState |
ローカルstate | v16.8 |
useEffect |
副作用 | v16.8 |
useContext |
Context読み取り | v16.8 |
useReducer |
複雑なstate | v16.8 |
useCallback |
関数のメモ化 | v16.8 |
useMemo |
値のメモ化 | v16.8 |
useRef |
ref・値の保持 | v16.8 |
useImperativeHandle |
ref公開制御 | v16.8 |
useLayoutEffect |
同期的副作用 | v16.8 |
useDebugValue |
DevTools用ラベル | v16.8 |
useId |
SSR対応ID | v18 |
useTransition |
非緊急更新 | v18 |
useDeferredValue |
値の遅延 | v18 |
useSyncExternalStore |
外部ストア購読 | v18 |
useInsertionEffect |
CSS-in-JS用 | v18 |
use |
Promise/Context読み取り | v19 |
useActionState |
Actionのstate管理 | v19 |
useFormStatus |
フォーム送信状態 | v19 |
useOptimistic |
楽観的UI更新 | v19 |
おわりに
Hooksは数が増えてきましたが、日常的に使うのはuseState・useEffect・useRef・useContextあたりで8割カバーできます。
残りは「こういうHookがあったな」と頭の片隅に置いておき、必要になった時にこの記事を開いて詳細を確認する、くらいの温度感でOKです。
React 19のServer ComponentsやActionsと組み合わせると、useActionStateやuseOptimisticが一気に活躍します。新しい書き方にも少しずつ慣れていきましょう。
記事の誤りや「この使い方もよく使う」という情報があれば、ぜひコメントで教えてください 🙏