レビュー済み·難易度: 中級·更新: 2026-04-14

Next.js 15 + Zustand でモーダル状態管理を作る

Zustand を使ってモーダルの開閉状態をグローバルに管理する実装例。複数モーダルへの対応と型安全な設計を示す。

nextjsstate-managementui-componentzustandtailwindcss

対応バージョン

nextjs 15react 19typescript 5zustand 5tailwindcss 4

前提環境

React の基本的な hooks の使い方を理解していること

概要

Zustand を使ってモーダルの開閉状態をグローバルに管理する。 複数モーダルを識別するため modalKey を使った設計にする。

インストール

npm install zustand

ストア定義

// src/stores/modalStore.ts
import { create } from "zustand";

type ModalKey = "confirm" | "profile" | "settings";

type ModalState = {
  openModals: Set<ModalKey>;
  open: (key: ModalKey) => void;
  close: (key: ModalKey) => void;
  isOpen: (key: ModalKey) => boolean;
};

export const useModalStore = create<ModalState>((set, get) => ({
  openModals: new Set(),
  open: (key) =>
    set((state) => ({ openModals: new Set(state.openModals).add(key) })),
  close: (key) => {
    const next = new Set(get().openModals);
    next.delete(key);
    set({ openModals: next });
  },
  isOpen: (key) => get().openModals.has(key),
}));

モーダルコンポーネント

// src/components/Modal.tsx
"use client";

import { useModalStore } from "@/stores/modalStore";

type Props = {
  modalKey: "confirm" | "profile" | "settings";
  title: string;
  children: React.ReactNode;
};

export function Modal({ modalKey, title, children }: Props) {
  const { isOpen, close } = useModalStore();

  if (!isOpen(modalKey)) return null;

  return (
    <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
      <div className="w-full max-w-md rounded-lg bg-white p-6 shadow-xl">
        <div className="mb-4 flex items-center justify-between">
          <h2 className="text-lg font-semibold">{title}</h2>
          <button
            onClick={() => close(modalKey)}
            className="text-gray-400 hover:text-gray-600"
            aria-label="閉じる"
          >
            ✕
          </button>
        </div>
        {children}
      </div>
    </div>
  );
}

利用例

"use client";

import { useModalStore } from "@/stores/modalStore";
import { Modal } from "@/components/Modal";

export function Page() {
  const { open } = useModalStore();

  return (
    <>
      <button onClick={() => open("confirm")}>確認モーダルを開く</button>
      <Modal modalKey="confirm" title="確認">
        <p>本当に実行しますか?</p>
      </Modal>
    </>
  );
}

ポイント

  • Set で複数モーダルを管理し、互いに干渉しない
  • isOpen を selector として使うことで必要なコンポーネントだけ再レンダリングされる

関連サンプル