next.jssanityhydrationnext.js14shadcnui

Next js 14 & ShadCN Error: Hydration failed because the initial UI does not match what was rendered on the server


I have a problem with a dynamic page in my next application, basically it is about a web site, in which through SANITY, I upload my work done. The idea is that when I click on a button, a Shadcn component is activated and shows me the data from sanity. I'm using Nextjs14, typescript, tailwind, sanity and Shadcn

I attach the code:

import ImageGallery from "@/app/components/ImageGallery"
import { client } from "../../../utils/sanity/client"

// UI SHADCN
import {
  Sheet,
  SheetContent,
  SheetDescription,
  SheetHeader,
  SheetTitle,
  SheetTrigger,
} from "@/components/ui/sheet"
import { Button } from "@/components/ui/button"
import Link from "next/link"

// SANITY DATA
async function getData(slug: string) {
    const querie = `*[_type == 'job' && slug.current == "${slug}"][0]{
        _id,
        images,
        name,
        description,
        objetive,
        releaseDate,
        link,
        "slug": slug.current,
        "categoryName": category->name,
    }`
    const data = client.fetch(querie)
    return data
}

export default async function JobUI({ params } : { params: {slug: string} }) {
    const data: job = await getData(params.slug)
    return(
        <div className="flex gap-3">
            <ImageGallery images={data.images}/>

            <Sheet >
                <SheetTrigger><Button>Ver Información</Button></SheetTrigger>
                <SheetContent className="w-96">
                    <SheetHeader>
                    <SheetTitle className="mt-5">
                        <p>{data.name}</p>
                        <p className="text-orange-500">Categoria: {data.categoryName}</p>
                    </SheetTitle>

                    <SheetDescription>
                        <p className="text-sm font-bold">Descripción</p>
                    <p>{data.description}</p>
                    </SheetDescription>

                    <SheetDescription>
                        <p className="text-sm font-bold">Objetivo</p>
                    <p>{data.objetive}</p>
                    </SheetDescription>

                    <Link href={data.link}  target="_blank"><Button>Link del Proyecto</Button></Link>
                    </SheetHeader>
                </SheetContent>
            </Sheet>
            
        </div>
    )
}

The application works without problems, only that from the browser, next js gives me this message of 3 serious errors, related to hydration and components! Message:

Unhandled Runtime Error
Error: Hydration failed because the initial UI does not match what was rendered on the server.

Warning: Expected server HTML to contain a matching <button> in <button>.

See more info here: https://nextjs.org/docs/messages/react-hydration-error

Component Stack
button
button
eval
./node_modules/@radix-ui/react-primitive/dist/index.mjs (39:26)
eval
./node_modules/@radix-ui/react-dialog/dist/index.mjs (90:28)
Provider
./node_modules/@radix-ui/react-context/dist/index.mjs (45:28)
$5d3850c4d0b4e6c7$export$3ddf2d174ce01153
./node_modules/@radix-ui/react-dialog/dist/index.mjs (60:28)
div
Call Stack
throwOnHydrationMismatch
node_modules\next\dist\compiled\react-dom\cjs\react-dom.development.js (7088:8)
throwOnHydrationMismatch
node_modules\next\dist\compiled\react-dom\cjs\react-dom.development.js (7117:6)
tryToClaimNextHydratableInstance
node_modules\next\dist\compiled\react-dom\cjs\react-dom.development.js (16524:4)
updateHostComponent$1
node_modules\next\dist\compiled\react-dom\cjs\react-dom.development.js (18427:13)
apply
node_modules\next\dist\compiled\react-dom\cjs\react-dom.development.js (20498:13)
dispatchEvent
node_modules\next\dist\compiled\react-dom\cjs\react-dom.development.js (20547:15)
apply
node_modules\next\dist\compiled\react-dom\cjs\react-dom.development.js (20622:28)
invokeGuardedCallback
node_modules\next\dist\compiled\react-dom\cjs\react-dom.development.js (26813:6)
beginWork
node_modules\next\dist\compiled\react-dom\cjs\react-dom.development.js (25637:11)
performUnitOfWork
node_modules\next\dist\compiled\react-dom\cjs\react-dom.development.js (25623:4)
workLoopConcurrent
node_modules\next\dist\compiled\react-dom\cjs\react-dom.development.js (25579:8)
renderRootConcurrent
node_modules\next\dist\compiled\react-dom\cjs\react-dom.development.js (24432:37)
callback
node_modules\next\dist\compiled\scheduler\cjs\scheduler.development.js (256:33)
workLoop
node_modules\next\dist\compiled\scheduler\cjs\scheduler.development.js (225:13)
flushWork
node_modules\next\dist\compiled\scheduler\cjs\scheduler.development.js (534:20)

I would appreciate your help please

I know I'm doing something wrong but I don't know what, I tried to add noHydratation on some buttons, but it gives me a bad feeling, it's not the solution.


Solution

  • The reason this hydration error occurs is using invalid HTML syntax like a <button> inside a <button>.

    In your case, this happens because the <SheetTrigger> renders a <button> and a <Button> is nested inside which also renders a <button>.

    To fix this issue, you can add an asChild prop set to true to the <SheetTrigger>. This will not render the default DOM element of SheetTrigger. Instead, it will render the child component <Button> and will pass the props and behaviour of <SheetTrigger> to make it functional.

    This is a Radix UI functionality, and you can read more about it in the Radix Composition guide.

    For your code, change your default export function to this:

    export default async function JobUI({ params }: { params: { slug: string } }) {
      const data: job = await getData(params.slug);
      return (
        <div className="flex gap-3">
          <ImageGallery images={data.images} />
    
          <Sheet>
            <SheetTrigger asChild>
              <Button>Ver Información</Button>
            </SheetTrigger>
            <SheetContent className="w-96">
              <SheetHeader>
                <SheetTitle className="mt-5">
                  <p>{data.name}</p>
                  <p className="text-orange-500">Categoria: {data.categoryName}</p>
                </SheetTitle>
    
                <SheetDescription>
                  <p className="text-sm font-bold">Descripción</p>
                  <p>{data.description}</p>
                </SheetDescription>
    
                <SheetDescription>
                  <p className="text-sm font-bold">Objetivo</p>
                  <p>{data.objetive}</p>
                </SheetDescription>
    
                <Link href={data.link} target="_blank">
                  <Button>Link del Proyecto</Button>
                </Link>
              </SheetHeader>
            </SheetContent>
          </Sheet>
        </div>
      );
    }