2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【保存版】React Hooks チートシート 〜全Hooksを実例付きで一気に総ざらい〜

2
Posted at

はじめに

Reactを書いていると「このHook、どう使うんだっけ?」となる瞬間、ありませんか?
本記事はそんな時にサッと開いて確認できるチートシートとして、React 19時点のHooksを網羅的にまとめたものです。

各Hookは 「一言で言うと」→「シグネチャ」→「最小コード例」→「落とし穴」 の順で整理しています。必要な場所にジャンプして使ってください。

対象バージョン: React 19(React 18以前での差分は都度補足します)


Hooksの基本ルール(最初に必ず押さえる)

React公式が定める Rules of Hooks です。違反するとバグるか、ESLintに怒られます。

  1. トップレベルでのみ呼ぶiffor・ネストした関数の中で呼ばない
  2. React関数の中でのみ呼ぶ — コンポーネントかカスタムHookの中だけ
  3. 名前は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>
    </>
  );
}

useStateuseReducer の使い分け:

ケース 推奨
単純な値(真偽値・数値・文字列) 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-hooksexhaustive-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は数が増えてきましたが、日常的に使うのはuseStateuseEffectuseRefuseContextあたりで8割カバーできます。
残りは「こういうHookがあったな」と頭の片隅に置いておき、必要になった時にこの記事を開いて詳細を確認する、くらいの温度感でOKです。

React 19のServer ComponentsやActionsと組み合わせると、useActionStateuseOptimisticが一気に活躍します。新しい書き方にも少しずつ慣れていきましょう。

記事の誤りや「この使い方もよく使う」という情報があれば、ぜひコメントで教えてください 🙏

参考

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?