useMyHookの戻り値が増えてきたら？オブジェクトで整理しよう！

# はじめに 

React のカスタムフックを作っていると、次のように「戻り値がどんどん増えてきた…」という経験はありませんか？

```tsx
// before: フラットな戻り値
return {
  count,
  setCount,
  isLoading,
  setLoading,
  error,
  resetError,
};
```

このようなコードは一見シンプルに見えますが、値が増えてくると以下のような課題が生まれます。

**💡 何が問題？**

| 課題         | 説明                        |
| ---------- | ------------------------- |
| **読みづらい**  | 何が何のための値なのか、ぱっと見でわからない。   |
| **管理しづらい** | 値を追加・削除するとフックを使う側にも影響が及ぶ。 |
| **拡張しにくい** | 状態が複雑になると、処理を分けづらくなる。     |

# 関係のある値をオブジェクトにまとめよう！

例えば上記の例を「状態」「エラー」「ローディング」に分類して整理すると、以下のように書けます。

```tsx
// after: オブジェクトにまとめる
return {
  counter: {
    count,
    setCount,
  },
  loading: {
    isLoading,
    setLoading,
  },
  error: {
    error,
    reset: resetError,
  },
};
```

**🧠 どう良くなったか？**

* `counter.count` など、**役割が明確になる**
* `const { error } = useMyHook();` など、**必要なものだけを取り出しやすい**
* 同じ責務ごとにオブジェクト化されているので、**再利用やテストもやりやすい**

# もっと大規模なフックには？

さらに状態が複雑な場合、以下のような構造にすると拡張性もバッチリです：

```ts
interface CounterControls {
  count: number;
  setCount: (n: number) => void;
}

interface LoadingState {
  isLoading: boolean;
  setLoading: (v: boolean) => void;
}

interface ErrorHandling {
  error: Error | null;
  reset: () => void;
}

export function useMyFeature(): {
  counter: CounterControls;
  loading: LoadingState;
  error: ErrorHandling;
} {
  // ...state and logic...
  return {
    counter: { count, setCount } as CounterControls,
    loading: { isLoading, setLoading } as LoadingState,
    error: { error, reset } as ErrorHandling,
  };
}
```

**📊 サマリ表**

| アプローチ      | メリット               | デメリット            |
| ---------- | ------------------ | ---------------- |
| フラットな戻り値   | 小さいフックにはシンプルで使いやすい | 大きくなると煩雑になりやすい   |
| オブジェクトで整理  | 可読性・保守性・拡張性が高い     | 少し冗長に見えることも      |
| カスタムフックで分割 | 責務ごとに分離できる         | ファイルが増えて複雑になることも |

# `page.tsx`での利用例

さきほどのようにオブジェクトで整理されたカスタムフックを使うと、呼び出し側のコードも読みやすくなります。

### `useMyFeature.ts`（カスタムフック）

```tsx:hooks/useMyFeature.ts
import { useState } from "react";

export function useMyFeature() {
  const [count, setCount] = useState(0);
  const [isLoading, setLoading] = useState(false);
  const [error, setError] = useState<Error | null>(null);

  const resetError = () => setError(null);

  return {
    counter: { count, setCount },
    loading: { isLoading, setLoading },
    error: { error, reset: resetError },
  };
}
```

### `page.tsx`（フックを使用）

```tsx:app/page.tsx
"use client";

import { useMyFeature } from "@/hooks/useMyFeature";

export default function Page() {
  const { counter, loading, error } = useMyFeature();

  return (
    <div className="p-4">
      <h1 className="text-xl font-bold mb-2">カウントアプリ</h1>

      <p>現在のカウント: {counter.count}</p>

      <button
        onClick={() => counter.setCount(counter.count + 1)}
        className="mt-2 px-4 py-2 bg-blue-500 text-white rounded"
        disabled={loading.isLoading}
      >
        +1
      </button>

      {loading.isLoading && <p className="text-gray-500">読み込み中...</p>}

      {error.error && (
        <div className="mt-4 text-red-600">
          <p>エラー: {error.error.message}</p>
          <button onClick={error.reset} className="underline text-sm">
            エラーをリセット
          </button>
        </div>
      )}
    </div>
  );
}
```

**👍 メリットを再確認**

* `counter.count` など意味が明確！
* `error.reset()` など**文脈での使用**がしやすい！
* 状態をグルーピングしておくことで、**コード分割や再利用がしやすい**！

# まとめ

カスタムフックが大きくなってきたら、**戻り値をオブジェクトにまとめて整理する**ことで：

* 可読性UP
* 管理のしやすさUP
* 拡張性UP

など、多くのメリットがあります。
今後カスタムフックを設計する際の選択肢として、ぜひ取り入れてみてください！