Next.js 13によるモーダルウィンドウ実装の奨め

2023年6月4日
nextjs
react
この記事では、Next.js 13 によるモーダルウィンドウ実装の奨めについて紹介します。

本記事の完成品です。

はじめに

今回は、Next.js 13 を使ったモーダルウィンドウの実装について解説します。この記事は、Web 開発において頻出するモーダルウィンドウの作成が初めての方、または実装方法をさらに簡単にしたいと考えている方に特にお勧めです。

本記事では、具体的なコード例と共にステップバイステップで進めていきますので、初心者の方でも安心して読み進められます。では、さっそく始めていきましょう。

モーダルウィンドウとは

既にご存知の方も多いと思いますが、モーダルウィンドウとは、Web サイトやアプリケーションの画面上に表示されるポップアップウィンドウのことです。モーダルウィンドウは、ユーザーの操作を妨げることなく、画面上に情報を表示することができるため、Web サイトやアプリケーションのユーザーインターフェースにおいて頻繁に利用されています。

ダイアログボックスやアラート、ポップアップウィンドウなど、様々な名称で呼ばれることがありますが、本記事ではモーダルウィンドウと統一して記述していきます。

実装

それでは、実際にモーダルウィンドウを実装していきましょう。

shadcn/ui のインストール

今回スタイルは、shadcn/uiを利用します。shadcn/ui は、Radix UI と Tailwind CSS を使用して構築された再利用可能なコンポーネントです。

新しいプロジェクトの依存関係を初期化するには、init コマンドを使用します。

pnpx shadcn-ui init

init コマンドは、依存関係をインストールし、cn util を追加し、tailwind.config.js を設定し、プロジェクト用の CSS 変数を設定します。

次に add コマンドでコンポーネントをプロジェクトに追加し、今回利用するモーダルウィンドウをインストールします。

pnpx shadcn-ui add dialog

ディレクトリ構成

今回は認証系のモーダルウィンドウを実装するため、AuthModalを作成する例を紹介します。 ディレクトリ構成は以下のようになります。

Image

components

Modal.tsxの実装は以下のようになります。

import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
 
interface ModalProps {
  isOpen: boolean;
  onChange: (open: boolean) => void;
  title: string;
  description: string;
  children: React.ReactNode;
}
 
const Modal: React.FC<ModalProps> = ({ isOpen, onChange, title, description, children }) => {
  return (
    <Dialog open={isOpen} defaultOpen={isOpen} onOpenChange={onChange}>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>{title}</DialogTitle>
          <DialogDescription>{description}</DialogDescription>
        </DialogHeader>
        {children}
      </DialogContent>
    </Dialog>
  );
};
 
export default Modal;

この Modal コンポーネントを利用して、AuthModal.tsxを作成します。

'use client';
 
import { useEffect } from 'react';
import { useRouter } from 'next/navigation';
 
import useAuthModal from '@/hooks/useAuthModal';
 
import Modal from './Modal';
 
const AuthModal = () => {
  const router = useRouter();
  const { onClose, isOpen } = useAuthModal();
 
  return (
    <Modal
      title="Title"
      description="lorem ipsum dolor sit amet, consectetur adipiscing elit."
      isOpen={isOpen}
      onChange={onChange}
    >
      <div className="flex flex-col space-y-4">
        <button className="btn btn-primary">Login with GitHub</button>
        <button className="btn btn-secondary">Login with Google</button>
      </div>
    </Modal>
  );
};
 
export default AuthModal;

今回は実装を行いませんが、AuthModal ではセッション情報を取得し、ログイン状態に応じて表示を切り替えることを想定しています。

hooks

useAuthModal.tsの実装は以下のようになります。

import { create } from 'zustand';
 
interface AuthModalStore {
  isOpen: boolean;
  onOpen: () => void;
  onClose: () => void;
}
 
const useAuthModal = create<AuthModalStore>((set) => ({
  isOpen: false,
  onOpen: () => set({ isOpen: true }),
  onClose: () => set({ isOpen: false }),
}));
 
export default useAuthModal;

ここでは、zustandcreateを利用して、isOpenonOpenonCloseの 3 つの状態を管理しています。

providers

アプリ全体で利用できるように、ModalProviders.tsxを作成します。

'use client';
 
import { useEffect, useState } from 'react';
 
import AuthModal from '@/components/AuthModal';
 
const ModalProviders: React.FC = () => {
  const [isMounted, setIsMounted] = useState(false);
 
  useEffect(() => {
    setIsMounted(true);
  }, []);
 
  if (!isMounted) {
    return null;
  }
 
  return (
    <>
      <AuthModal />
    </>
  );
};
 
export default ModalProviders;

ここでのコードを解説します。

  • isMountedは、useEffectを利用して、初回レンダリング時にtrueになります。
  • isMountedfalseの場合は、nullを返します。
  • isMountedtrueの場合は、AuthModalを返します。

ここでの実装は、AuthModalのみですが、アプリケーション全体で利用するモーダルウィンドウがある場合は、ここで管理することをおすすめします。

Layout

最後に、Layout.tsxModalProvidersを読み込みます。

import { Inter } from 'next/font/google';
 
import ModalProviders from '@/providers/ModalProvider';
 
import './globals.css';
 
const inter = Inter({ subsets: ['latin'] });
 
export const metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
};
 
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <ModalProviders />
        {children}
      </body>
    </html>
  );
}

これで、モーダルウィンドウの実装は完了です。

モーダルウィンドウの表示

モーダルウィンドウを表示するには、useAuthModalで定義したonOpenを呼び出します。 今回のデモではHeader.tsxでボタンを作成し、クリック時にonOpenを呼び出します。

'use client';
 
import Link from 'next/link';
 
import { Button } from '@/components/ui/button';
import useAuthModal from '@/hooks/useAuthModal';
 
const Header = () => {
  const { onOpen } = useAuthModal();
 
  return (
    <>
      <div className="fixed  top-0 left-0 right-0 z-50 bg-primary-foreground">
        <div className="container flex justify-between items-center py-4 border-b border-b-white backdrop-blur-md">
          <Link href="/">LOGO</Link>
          <div className="flex gap-2 items-center">
            <Button onClick={onOpen}>openModal</Button>
          </div>
        </div>
      </div>
    </>
  );
};
 
export default Header;

これで、モーダルウィンドウが表示されるようになりました。

まとめ

今回は、Next.js でモーダルウィンドウを実装する方法を紹介しました。 モーダルウィンドウの実装は、アプリケーションによって異なるため、今回の実装をそのまま利用することはできませんが、モーダルウィンドウの実装に必要な要素を紹介できたと思います。