How to Create a Reusable Modal in Next.js Using Context
In this article, we’ll walk through how to create a reusable modal in Next.js using React Context. This approach allows you to trigger the modal from any component in your application without having to pass props down multiple levels. We’ll also use shadcn/ui
for the modal and button components.
Step 1: Setting Up the Modal Provider
First, let’s create a context provider to manage the modal’s state. This provider will allow any component in your app to open or close the modal.
Create a file components/modals/providers.tsx
and paste the following code:
"use client";
import { createContext, Dispatch, SetStateAction } from "react";
import { useSignInModal } from "./sign-in-modal";
// Create a context for the modal
export const ModalContext = createContext<{
setShowSignInModal: Dispatch<SetStateAction<boolean>>;
}>({
setShowSignInModal: () => {},
});
// ModalProvider component to wrap your app with
export default function ModalProvider({
children,
}: Readonly<{ children: React.ReactNode }>) {
const { setShowSignInModal, SignInModal, showSignInModal } = useSignInModal();
return (
<ModalContext.Provider
value={{
setShowSignInModal,
}}
>
{showSignInModal && <SignInModal />}
{children}
</ModalContext.Provider>
);
}
Explanation:
ModalContext
: This is a React context that holds the state and function to control the modal. It providessetShowSignInModal
to any component that consumes it.ModalProvider
: This is a wrapper component that provides the context to its children. It uses theuseSignInModal
hook (which we'll define next) to manage the modal's state and render the modal whenshowSignInModal
istrue
.
Step 2: Creating the Modal UI
Next, let’s create the modal component. We’ll use shadcn/ui
for the Dialog
and Button
components.
Create a file components/modals/sign-in-modal.tsx
and paste the following code:
"use client";
import { Dispatch, SetStateAction, useState } from "react";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Button } from "../ui/button";
import { IceCream, Loader2 } from "lucide-react";
// SignInModal component
function SignInModal({
showSignInModal,
setShowSignInModal,
}: {
showSignInModal: boolean;
setShowSignInModal: Dispatch<SetStateAction<boolean>>;
}) {
const [signInClicked, setSignInClicked] = useState(false);
return (
<Dialog open={showSignInModal} onOpenChange={setShowSignInModal}>
<DialogContent>
<DialogHeader>
<DialogTitle className="text-center">Welcome to WebApp</DialogTitle>
<DialogDescription className="text-center">
Sign in to your account to continue
</DialogDescription>
</DialogHeader>
<Button
variant="outline"
className="w-full"
disabled={signInClicked}
onClick={() => {
setSignInClicked(true);
setTimeout(() => {
setShowSignInModal(false);
setSignInClicked(false);
}, 1000);
}}
>
{signInClicked ? (
<Loader2 className="mr-2 size-4 animate-spin" />
) : (
<IceCream className="mr-2 size-4" />
)}
Sign In with Google
</Button>
</DialogContent>
</Dialog>
);
}
// useSignInModal hook to manage modal state
export function useSignInModal() {
const [showSignInModal, setShowSignInModal] = useState(false);
const SignInModalComponent = () => {
return (
<SignInModal
showSignInModal={showSignInModal}
setShowSignInModal={setShowSignInModal}
/>
);
};
return {
setShowSignInModal,
showSignInModal,
SignInModal: SignInModalComponent,
};
}
Explanation:
SignInModal
: This is the modal component. It usesshadcn/ui
'sDialog
component to create a modal with a title, description, and a button. The button simulates a sign-in process with a loading state.useSignInModal
: This is a custom hook that manages the state of the modal (showSignInModal
) and provides a function (setShowSignInModal
) to toggle the modal. It also returns theSignInModal
component.
Step 3: Installing shadcn/ui
Components
To use the Dialog
and Button
components from shadcn/ui
, you need to install them:
- Install
shadcn/ui
(if not already installed):
npx shadcn-ui@latest init
2. Add the Dialog
and Button
components:
npx shadcn-ui@latest add dialog button
Step 4: Using the Modal in a Component
Now that the modal and provider are set up, you can trigger the modal from any component. Let’s create a Navbar
component that opens the modal when a button is clicked.
Create a file components/navbar.tsx
and paste the following code:
"use client";
import Link from "next/link";
import { Button } from "./ui/button";
import { useContext } from "react";
import { ModalContext } from "./modals/providers";
export default function Navbar() {
const { setShowSignInModal } = useContext(ModalContext);
return (
<div className="border-b">
<div className="h-14 container mx-auto flex items-center justify-between">
<Link href="/">Logo</Link>
<Button onClick={() => setShowSignInModal(true)}>Login</Button>
</div>
</div>
);
Explanation:
Navbar
: This component uses theModalContext
to access thesetShowSignInModal
function. When the "Login" button is clicked, it setsshowSignInModal
totrue
, which opens the modal.
Step 5: Wrapping Your App with the Modal Provider
Finally, wrap your app (or the part of your app where you want the modal to be available) with the ModalProvider
. For example, in app/layout.tsx
:
import { ModalProvider } from "@/components/modals/providers";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<ModalProvider>{children}</ModalProvider>
</body>
</html>
);
}
Summary
ModalProvider
: Manages the modal's state and provides it to the rest of the app.SignInModal
: The UI for the modal.useSignInModal
: A custom hook to manage the modal's state.Navbar
: A component that triggers the modal using the context.
With this setup, you can easily trigger the modal from any component by importing the context and calling setShowSignInModal
.
If you’d like to see a video tutorial, check out this link.