ui icon indicating copy to clipboard operation
ui copied to clipboard

Toast is not displayed in useEffect during intial render

Open tjhoo opened this issue 1 year ago • 3 comments

Hi,

Okay, I'm not sure if this is a shadcn ui issue or not ...

I'm developing Next.js 14 application with shadcn ui. I have a server action like this,

"use server";
...
export const createMessage = async (currentState) => {
  return {
    message: "Success";
  };
}

then, I have a client component

"use client";
...
const Chat = () => {

  // formState used for notification after server action
  const [formState, formAction] = useFormState(createMessage, {
    message: "empty",
  });
  
  useEffect(() => {
    // this logged '{ message: "empty" }' in the initial rendering
    console.log("%s", JSON.stringify(formState));
    // the toast is not display during the initial render but it will show "success" after the following form is submitted.
    toast({ description: formState.message });
  }, [toast, formState]);
  
  return (
    <form action={formAction}>
      <Button type="submit">Create message</Button>
    </form>
  );
}

export default Chat ;

Why the toast is not shown when the page was first loaded (and the useEffect was triggered and the empty message is logged in the console)?

tjhoo avatar Dec 24 '23 09:12 tjhoo

The console.log function works immediately upon component render because it's a synchronous operation.

However, the toast function likely depends on the DOM being fully ready or a ToastProvider component being mounted in your app. If these conditions aren't met when the toast function is called, the toast notification might not display.

import { Toaster } from "@/components/ui/toaster"
 
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <head />
      <body>
        <main>{children}</main>
        <Toaster /> //this component might not be ready when the toast function is called
      </body>
    </html>
  )
}

This explanation is based on my experience, There may be other causes for this issue but this is also a case.

jrTilak avatar Dec 24 '23 15:12 jrTilak

im having a similar issue where <Toaster/> doesn't display toasts, But if I copy the content of <Toaster/> and paste it in my app it works fine

TheMikeyRoss avatar Jan 13 '24 02:01 TheMikeyRoss

Try extracting the toast function from useEffect and wrap it inside a function:

"use client";

import * as React from "react";
import { toast } from "sonner";

export default function Home() {
  const showToast = () => toast("hello");
  React.useEffect(() => {
    showToast();
  }, [showToast]);
}

alamenai avatar Jan 14 '24 02:01 alamenai

This issue has been automatically closed because it received no activity for a while. If you think it was closed by accident, please leave a comment. Thank you.

shadcn avatar Feb 28 '24 23:02 shadcn

This is the answer you are looking for, based on the official sonner docs https://sonner.emilkowal.ski/toast#render-toast-on-page-load

Render toast on page load To render a toast on initial page load it is required that the function toast() is called inside of a setTimeout or requestAnimationFrame.

setTimeout(() => { toast('My toast on a page load'); });

rajeshdavidbabu avatar Apr 10 '24 08:04 rajeshdavidbabu

For me what worked is to put <Toaster /> in my _app.jsx along with timer.

_app.js

export default function App({ Component, pageProps }) {
  
  return (
    <UserProvider>

      <Component {...pageProps} />

      <Toaster />


    </UserProvider>
  )
}

Dashboard.jsx

useEffect(() => {
      const timeoutId = setTimeout(() => {
          toast.error('My toast on a page load');
      }, 1000);
  
      return () => clearTimeout(timeoutId); // Cleanup the timeout on component unmount
  }, []);

srilokhkaruturi avatar May 24 '24 16:05 srilokhkaruturi

Solution 1: For me, simply rendering the <Toaster /> component before <main>{children}</main> worked.

import { Toaster } from "@/components/ui/toaster"
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <head />
      <body>
        <Toaster /> // Render it before the rest of the application
        <main>{children}</main>
      </body>
    </html>
  )
}

Solution 2: From docs:

useEffect(() => {
  const timer = setTimeout(() => {
     toast.info('your message here')
  }, 100);

  return () => clearTimeout(timer);
}, []);

AminPainter avatar Aug 03 '24 05:08 AminPainter