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 />
</>
);
}
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}
/>
</>
);
}
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>
)
}
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,
});
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);
}
};
And use it like this:
showToast("success" <p>Your post has been published!</p>);
This way, you can maintain consistency in handling toasts in your app.
I hope you find this article helpful. Thank you!
Top comments (8)
I dont know why i get still hydration error
and this is my ToasProvider
Unhandled Runtime Error
Error: Attempted to call error() from the server but error is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.
Source
src/app/components/Toast/toast.ts (37:20) @ error
35 | return toast.success(content, optionsToApply);
36 | case "error":
I got this error if I use showToast in the server side component. If I use it in client side , there is no error. Why ?
For example, the window object doesn't exist server side, everything that happens there happens before you reach the browser, so any library that handles the UI, on the fly, needs the component to be client side too... hope that make sense?
Great job! Thanks!
Obrigado por compartilhar!
Thank you for this piece, well written too . Life saver !!
Very useful, thank you for your helper function
I just loved your helper function! Very useful! Thanks for sharing!