概要
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 として使うことで必要なコンポーネントだけ再レンダリングされる