next.jstoastreact-server-components

How to show toasts in react server components?


I'm using Next.js 14 with the app router.

I have two server components:

  1. List page /products that shows multiple products
  2. Product Details page /products/{id}.

On /products/{id} I check whether the id is available in my products database with a server action and then render the product.

If the product is not available, I wanted to redirect the user back to /products and show a toast message that the product was not found.

Redirecting is simple enough:

import { redirect } from 'next/navigation'

async function getNoProduct() {
  return null
}

export default async function RedirectFromPage() {
  
  let res = await getNoProduct()
  if (!res === null) {
    redirect('/products')
  }
  return (
    <div>
      <p>Hello SO</p>
    </div>
  )
}

However, I'm stuck implementing the toast message (using sonner toast via shadcn).

Directly adding the toast like below doesn't work because /products/{id} is a server component and toast relies on useEffect, e.g.

import { redirect } from 'next/navigation'
import { useToast } from '@/components/ui/use-toast'

async function getNoProduct() {
  return null
}

export default async function RedirectFromPage() {
  const { toast } = useToast()

  let res = await getNoProduct()
  if (!res === null) {
    // Show toast
    toast({
      title: 'Error',
      description: 'Product not found',
    })
    redirect('/products')
  }
  return (
    <div>
      <p>Hello SO</p>
    </div>
  )
}

will result in an error Error: useState only works in Client Components. Add the "use client" directive at the top of the file to use it. Read more: https://nextjs.org/docs/messages/react-client-hook-in-server-component

Based on another SO question related to server actions (here) I thought I could use cookies to communicate the error to /products and then render a client component that uses the toast but ran into issues because I can trigger these server actions only from a client component.

How should I approach this? Is toast just the wrong type of component here or is there a better way to do this?


Solution

  • My understanding is the toast packages are meant for client side. For server side, I think you can create a loading.tsx page and nextjs will show the loading page then the intended page. If it’s a client side component inside a server rendered page, you should be able to use React Suspense to wrap the client side component in your server rendered page