📝

【React修行日記】カスタムHookの作成

に公開

学習の目的

  • カスタムフックの基本とルールを理解する
  • useLocalStorageを作成して実装してみる

カスタムフックとは

カスタムフックは、Reactのフック(useStateuseEffectなど)を組み合わせて作る、再利用可能なロジックの集まり。これを使うことで、コンポーネント間で状態管理や副作用のロジックを簡単に共有できる

例えば、フォームの入力値を管理するロジックや、APIからデータを取得するロジックなど、複数のコンポーネントで共通して使いたい処理をカスタムフックとして切り出すことで、コードの重複を避け、保守性を高めることができる

カスタムフックのルール

1. 関数名はuseで始める

Reactのフックはすべてuseで始まる関数名を持っているため、カスタムフックも同様に命名することで、Reactのフックとしての特性を明示する。

2. フック内で他のフックを呼び出す

カスタムフック内でuseStateuseEffectなどのReactのフックを使って、状態管理や副作用の処理を行う。

カスタムフックのメリット

  • ロジックの再利用が簡単
    • 同じ処理を複数のコンポーネントで使い回せる
    • 例:フォームの入力管理、テーマ切替、APIデータ取得など
  • コンポーネントがシンプルになる
    • 状態管理や副作用のロジックをコンポーネントから切り離せる
    • コンポーネントはUIの描画だけに集中できる
  • テストがしやすい
    • ロジックを切り出すことで、UI に依存せず単体でテスト可能
    • バグの検出や修正が効率的

useLocalStorageの作成

「localStorageに値を保存・取得できるカスタムフック」を作ってみた...!

// useLocalStorage.tsx
import { useState, useEffect } from "react";

export function useLocalStorage<T>(key: string, initialValue: T) {
  const [value, setValue] = useState<T>(() => {
    if (typeof window === "undefined") return initialValue;
    const stored = localStorage.getItem(key);
    return stored ? JSON.parse(stored) : initialValue;
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue] as const;
}

useLocalStorageの役割と使用例

このフックを使うと、状態を更新するたびに自動でlocalStorageに保存され、ページをリロードしてもデータが保持される。
前回作成したテーマ切替に導入し、「ライトモード」や「ダークモード」を選んだ状態を保存できるようになった。

// ThemeProvider.tsx
// useStateの代わりにuseLocalStorageを使用
const [theme, setTheme] = useLocalStorage<Theme>("theme", "light");;

このように使うと、themeの値が変わるたびに自動でlocalStorageに保存され、ページを再読み込みしても、前回選んだテーマが保持される。

他にも以下のような用途でも有効:

  • ユーザーの言語設定
  • フィルタや検索条件
  • 入力フォームの下書き
  • UI状態の保存

など

コードの仕組み

1. ジェネリクス<T>で型を柔軟に

export function useLocalStorage<T>(key: string, initialValue: T) {
  const [value, setValue] = useState<T>(() => {
  • <T>は「扱う値の型を呼び出し時に指定できる」仕組み
  • 文字列、数値、オブジェクトなど、どんな型でも保存可能

2. 状態の初期化

const [value, setValue] = useState<T>(() => {
  if (typeof window === "undefined") return initialValue;
  const stored = localStorage.getItem(key);
  return stored ? JSON.parse(stored) : initialValue;
});
  • 初回レンダリング時に localStorage から値を読み込む
  • 保存されていなければinitialValueを使う
  • サーバーサイドレンダリングではwindowが存在しないため、initialValueを返す
  • 初期化関数を使うことで レンダリングのたびにlocalStorageを読み込まないようになっている

3. 値が変わったら保存

useEffect(() => {
  localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
  • valueが更新されるたびにlocalStorageに保存
  • keyも依存配列に入れることで、もしキーを変更した場合にも対応できる

4. 返り値をタプル型に固定

return [value, setValue] as const;
  • [value, setValue]の順番を固定
  • TypeScriptが正しい型を推論してくれる
  • これにより、呼び出す側で安全に[state, setState]の形で使える

まとめ

  • カスタムフックは再利用可能なロジックをまとめた自作フック
  • カスタムフックを作るときはuseで始め、他のフックを含める
  • useLocalStorageを使うと状態を自動でlocalStorageに保存可能

参考

https://ja.react.dev/learn/reusing-logic-with-custom-hooks

Discussion