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 (9)

Collapse
 
zkiezun profile image
Zuzanna Kieżun • Edited

It is unnecessary to write such Provider component, no need to pass children as props. I suggest doing this simpler way :)

'use client'

import 'react-toastify/dist/ReactToastify.css'
import { ToastContainer } from 'react-toastify'

const ClientSideToastContainer = () => <ToastContainer />

export default ClientSideToastContainer
Enter fullscreen mode Exit fullscreen mode

with layout like that:

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <ClientSideToastContainer />
          {children}
      </body>
    </html>
  )
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
alidurul profile image
Ali Durul

I dont know why i get still hydration error

<html lang="en" >
      <body className={inter.className} >
        <SessionProvider session={session} refetchInterval={60 * 60 * 24 * 3}>
          <ToastProvider>
          {children}
          </ToastProvider>
        </SessionProvider>
      </body>
    </html>
Enter fullscreen mode Exit fullscreen mode

and this is my ToasProvider

"use client";

import { Flip, ToastContainer } from 'react-toastify'
import 'react-toastify/dist/ReactToastify.css';

interface ToastProviderProps {
    children: React.ReactNode;
}

export default function ToastProvider({ children }: ToastProviderProps) {
    return (
        <>
            {children}
            <ToastContainer
                position="top-right"
                autoClose={2000}
                hideProgressBar={false}
                newestOnTop
                closeOnClick
                rtl={false}
                pauseOnFocusLoss
                draggable
                pauseOnHover
                theme="dark"
                transition={Flip}
            />
        </>
    );
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
mehmet_canboz_33fa384a3c profile image
Mehmet Can BOZ

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":

37 | return toast.error(content, optionsToApply);
| ^
38 | case "info":
39 | return toast.info(content, optionsToApply);
40 | case "warning":
Call Stack

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 ?

Collapse
 
nunes_silva_c9f91fd19066f profile image
Nunes Silva

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?

Collapse
 
valb268 profile image
Š’Š°Š»ŠµŠ½Ń‚ŠøŠ½ Š‘Ń‹Ń‚ŠµŠ½ŃŠŗŠøŠ¹

Great job! Thanks!

Collapse
 
osvaldocariege06 profile image
EdvaldoCariege

Obrigado por compartilhar!

Collapse
 
simplyvoda profile image
Vodina Efem

Thank you for this piece, well written too . Life saver !!

Collapse
 
gurbanmyradowserdar profile image
Serdar

Very useful, thank you for your helper function

Collapse
 
matheusdamiao profile image
Matheus DamiĆ£o

I just loved your helper function! Very useful! Thanks for sharing!