はじめに

ReduxやZustand、Recoilなどの状態管理ツールを使っていると、つい「状態は全部そこに集約しておけばいい」と思ってしまいがちです。
しかし実務では、UIの一時的なロジックや表示制御までもグローバルステートに逃がしてしまい、逆に構造が複雑化しているケースがよく見受けられます。

この記事では、レビューアーとして「それ、グローバルで持つ必要ある?」という問いを立てながら、構造の整理と提案の観点を解説します。

グローバルステートに逃げすぎた例

store.ts
export const useGlobalStore = create((set) => ({
  user: null,
  sidebarOpen: false,
  modalVisible: false,
  selectedTab: 0,
  toast: null,
  // ...
}));

UIコンポーネント側はこうなっています:

Sidebar.tsx
const { sidebarOpen, setSidebarOpen } = useGlobalStore();
return (
  <aside className={sidebarOpen ? 'open' : ''}>
    <button onClick={() => setSidebarOpen(false)}>閉じる</button>
  </aside>
);
@Reviewer
sidebarOpenのようなUI制御フラグは、グローバルで持つよりローカルで管理した方が構造の分離が明確になります。
  • 状態がアプリ全体に共有されていることで依存が不要に波及
  • コンポーネント単位での状態把握がしづらくなる
  • 構造的に「なぜその状態が必要なのか」が不透明になる

レビュー時にチェックすべきポイント

観点 チェック内容 検討すべき対応
状態が画面全体に関与しているか? 複数コンポーネントで使われているか? グローバルに置くべき
状態の寿命は短期的か? 1画面内で完結するか? ローカルに持たせる
状態の意図がUI制御に限定されていないか? 表示/非表示、開閉など useStateや局所Contextで代替可
他の状態との依存があるか? 複数ロジックに依存するなら reducer化または独立管理を検討

ローカルに戻した例

Sidebar.tsx
export function Sidebar() {
  const [open, setOpen] = useState(false);
  return (
    <aside className={open ? 'open' : ''}>
      <button onClick={() => setOpen(false)}>閉じる</button>
    </aside>
  );
}

このように戻すことで:

  • 構造的な局所性が明確になる
  • 他の機能との依存が減る
  • 状態の意図がその場で理解できる

グローバルで持つべき状態の例

逆に、以下のような状態はグローバルで持つべきです。

  • ログインユーザー情報(user
  • アプリ全体のトースト通知(toast
  • タブや言語など、複数画面にまたがるUIの共通制御

その上で、UIフラグとの混在を避けることが重要です。

状態構造を可視化

UML Diagram
UML Diagram

後者の構造の方が、状態の責務とスコープが明確になっており、保守性が高い設計です。

コメントテンプレート(レビュー用)

グローバル化の指摘
@Reviewer: sidebarOpenのようなUI局所制御の状態がグローバルに置かれており、構造的に不要な依存が発生しています。useStateでローカルに戻すことで、責務が明確になります。
改善提案例
- const { sidebarOpen, setSidebarOpen } = useGlobalStore();
+ const [open, setOpen] = useState(false);

まとめ

状態管理ライブラリは強力ですが、「何でも集めればよい」という構造は逆効果になることがあります。

レビューアーとしては、状態が本当に「アプリ全体で共有されるべきものか?」を見極め、
構造の粒度・寿命・責務という観点で、ローカル管理かグローバル管理かを判断していくことが大切です。

構造がシンプルで意図が明確なコードこそ、チーム全体で読みやすく、安全に育てていけるReactアプリケーションの土台になります。