React Hooksを使いこなす - 実践的なパターン集


React Hooksとは

React Hooksは、関数コンポーネントでstateやライフサイクルなどのReactの機能を使えるようにする仕組みです。クラスコンポーネントを使わずに、よりシンプルなコードが書けるようになりました。

基本のHooks

useState - 状態管理

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>カウント: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        増やす
      </button>
    </div>
  );
}

複数の状態を管理する場合:

function UserForm() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [age, setAge] = useState(0);

  // またはオブジェクトで管理
  const [user, setUser] = useState({
    name: '',
    email: '',
    age: 0
  });

  const handleChange = (field, value) => {
    setUser(prev => ({
      ...prev,
      [field]: value
    }));
  };
}

useEffect - 副作用の処理

import { useEffect } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    // データの取得
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(data => setUser(data));

    // クリーンアップ関数(オプショナル)
    return () => {
      // コンポーネントのアンマウント時に実行
      console.log('Cleanup');
    };
  }, [userId]); // userIdが変わった時のみ実行

  if (!user) return <div>読み込み中...</div>;

  return <div>{user.name}</div>;
}

useCallback - 関数のメモ化

import { useCallback } from 'react';

function TodoList() {
  const [todos, setTodos] = useState([]);

  // 関数をメモ化して不要な再生成を防ぐ
  const addTodo = useCallback((text) => {
    setTodos(prev => [...prev, { id: Date.now(), text }]);
  }, []); // 依存配列が空なので、関数は一度だけ生成される

  const removeTodo = useCallback((id) => {
    setTodos(prev => prev.filter(todo => todo.id !== id));
  }, []);

  return (
    <div>
      <TodoInput onAdd={addTodo} />
      <TodoItems items={todos} onRemove={removeTodo} />
    </div>
  );
}

useMemo - 計算結果のメモ化

import { useMemo } from 'react';

function ExpensiveComponent({ items }) {
  // 重い計算をメモ化
  const sortedItems = useMemo(() => {
    console.log('ソート処理実行');
    return items.sort((a, b) => a.value - b.value);
  }, [items]); // itemsが変わった時のみ再計算

  return (
    <ul>
      {sortedItems.map(item => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}

カスタムHooks

独自のHooksを作成することで、ロジックを再利用できます:

useLocalStorage - ローカルストレージ管理

function useLocalStorage(key, initialValue) {
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.error(error);
      return initialValue;
    }
  });

  const setValue = (value) => {
    try {
      setStoredValue(value);
      window.localStorage.setItem(key, JSON.stringify(value));
    } catch (error) {
      console.error(error);
    }
  };

  return [storedValue, setValue];
}

// 使用例
function App() {
  const [theme, setTheme] = useLocalStorage('theme', 'light');

  return (
    <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
      テーマ切り替え: {theme}
    </button>
  );
}

useFetch - データ取得

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    setLoading(true);
    fetch(url)
      .then(res => res.json())
      .then(data => {
        setData(data);
        setError(null);
      })
      .catch(err => {
        setError(err);
        setData(null);
      })
      .finally(() => {
        setLoading(false);
      });
  }, [url]);

  return { data, loading, error };
}

// 使用例
function UserList() {
  const { data, loading, error } = useFetch('/api/users');

  if (loading) return <div>読み込み中...</div>;
  if (error) return <div>エラー: {error.message}</div>;

  return (
    <ul>
      {data.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

useDebounce - デバウンス処理

function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => {
      clearTimeout(handler);
    };
  }, [value, delay]);

  return debouncedValue;
}

// 使用例:検索ボックス
function SearchBox() {
  const [searchTerm, setSearchTerm] = useState('');
  const debouncedSearchTerm = useDebounce(searchTerm, 500);

  useEffect(() => {
    if (debouncedSearchTerm) {
      // API呼び出しは0.5秒後に実行される
      fetch(`/api/search?q=${debouncedSearchTerm}`)
        .then(res => res.json())
        .then(data => console.log(data));
    }
  }, [debouncedSearchTerm]);

  return (
    <input
      value={searchTerm}
      onChange={(e) => setSearchTerm(e.target.value)}
      placeholder="検索..."
    />
  );
}

Hooksのルール

  1. トップレベルでのみ呼び出す:ループ、条件分岐、ネストした関数の中ではHooksを呼び出さない
  2. Reactの関数内でのみ呼び出す:通常のJavaScript関数からはHooksを呼び出さない

まとめ

React Hooksを使うことで、コンポーネントのロジックをシンプルに保ちながら、再利用可能な形で実装できます。カスタムHooksを活用することで、よりクリーンで保守性の高いコードが書けるようになります。

ぜひ自分のプロジェクトでHooksを活用してみてください!