DEV Community

koyablue
koyablue

Posted on

How to use React-Toastify with Next.js App router

React-Toastify is one of the most popular toast UI libraries for React or Next.js. It's easy to configure and use, but integrating it with the App router makes the configuration part a bit tricky.
There is an open issue about this topic, and while you can find some solutions there, they aren't summarized.
So in this article, I'll provide a summarized version of the solution to integrate React-Toastify with Next.js App router.

ToastProvider component as a client component wrapper for ToastContainer

The ToastContainer should be placed inside a client component. In the Pages router, you can simply put the ToastContainer component in the root component, such as App.tsx. However, in the App component, the root component is app/layout.tsx, which is a server component by default. So you first need to create a client component wrapper like this:

"use client";

import "react-toastify/dist/ReactToastify.css";
import "../../app/globals.css";
import { ToastContainer } from "react-toastify";

interface ToastProviderProps {
  children: React.ReactNode;
}

export default function ToastProvider({ children }: ToastProviderProps) {

  return (
    <>
      {children}
      <ToastContainer />
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

If you need to change the default configuration, you can do it in this component. The sample code below uses Tailwind CSS:

"use client";

import "react-toastify/dist/ReactToastify.css";
import "../../app/globals.css";
import { ToastContainer } from "react-toastify";

interface ToastProviderProps {
  children: React.ReactNode;
}

export default function ToastProvider({ children }: ToastProviderProps) {
  const contextClass = {
    success: "bg-blue-600",
    error: "bg-red-600",
    info: "bg-gray-600",
    warning: "bg-orange-400",
    default: "bg-indigo-600",
    dark: "bg-white-600 font-gray-300",
  };

  return (
    <>
      {children}
      <ToastContainer
        toastClassName={(context) =>
          contextClass[context?.type || "default"] +
          " relative flex p-1 min-h-10 rounded-md justify-between overflow-hidden cursor-pointer"
        }
        bodyClassName={() => "text-sm font-white font-med block p-3"}
        position="bottom-left"
        autoClose={3000}
      />
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Use ToastProvider in the root layout component

After creating the ToastProvider component, simply place it in the root layout as a parent component of children.

import type { Metadata } from "next"
import { Inter } from "next/font/google"
import Favicon from "@/app/icon.ico";
import "./globals.css"
import ToastProvider from "@/lib/react-toastify/ToastProvider"

const inter = Inter({ subsets: ["latin"] })

export const metadata: Metadata = {
  title: "Toast app",
  description: "Toast app is just a sample app for demonstrating react-toastify library.",
  icons: [{ rel: 'icon', url: Favicon.src }]
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <ToastProvider>
          {children}
        </ToastProvider>
      </body>
    </html>
  )
}

Enter fullscreen mode Exit fullscreen mode

And that's it. Now, you can use the beautiful toast UIs of React-Toastify anywhere you want!

toast('๐Ÿฆ„ Wow so easy!', {
  position: "top-right",
  autoClose: 5000,
  hideProgressBar: false,
  closeOnClick: true,
  pauseOnHover: true,
  draggable: true,
  progress: undefined,
  theme: "light",
  transition: Bounce,
});
Enter fullscreen mode Exit fullscreen mode

By the way, this is a bit off-topic, but in my project, I often implement a helper function to show toasts:

import { toast, ToastContent, ToastOptions, Slide, Id } from "react-toastify";


export const defaultToastOptions: ToastOptions = {
  position: "top-center",
  autoClose: 4000,
  hideProgressBar: true,
  closeOnClick: true,
  pauseOnHover: true,
  draggable: true,
  progress: undefined,
  theme: "colored",
  transition: Slide,
};

type ToastType = "success" | "error" | "info" | "warning" | "default";

/**
 * Display toast
 *
 * @param {ToastType} type
 * @param {ToastContent} content
 * @param {ToastOptions} [options=defaultToastOption]
 * @return {Id}
 */
export const showToast = (
  type: ToastType,
  content: ToastContent,
  options: Partial<ToastOptions> = {},
): Id => {
  const optionsToApply = { ...defaultToastOptions, ...options };

  switch (type) {
    case "success":
      return toast.success(content, optionsToApply);
    case "error":
      return toast.error(content, optionsToApply);
    case "info":
      return toast.info(content, optionsToApply);
    case "warning":
      return toast.warn(content, optionsToApply);
    case "default":
      return toast(content, optionsToApply);
    default:
      return toast(content, optionsToApply);
  }
};
Enter fullscreen mode Exit fullscreen mode

And use it like this:

showToast("success" <p>Your post has been published!</p>);
Enter fullscreen mode Exit fullscreen mode

This way, you can maintain consistency in handling toasts in your app.

I hope you find this article helpful. Thank you!

Top comments (0)