I've run into a hydration issue that's really confusing me. I have a simple component that displays a single list element with an email address and name if there is one.
Here's the original component. It works perfectly when running in dev mode, and displays fine in a deployed state, but throws multiple hydration errors:
export default function Email({ email, name }: EmailProps) {
return (
<li className="ml-6">
{name ? `${name} <${email}>` : email}
</li>
);
}
After some trial and error, I was able to pinpoint the issue was with the template literal. When I remove the template literal, and instead use four separate strings, the hydration errors go away.
export default function Email({ email, name }: EmailProps) {
return (
<li className="ml-6">
{name ? (
<>{name}{" <"}{email}{">"}</>
) : (
email
)}
</li>
);
}
I don't see any reason why the template literal should be causing hydration errors. Any ideas?
Edit: Here's the page.tsx
:
export default async function Details({ params: { customerId } }: DetailsProps) {
unstable_noStore();
const customer = await database.customer.findUnique({
where: { id: customerId },
include: {
emails: true,
},
});
if (!customer) {
return notFound();
}
return (
<main className="p-6">
<h1 className="mb-6 text-2xl">{`${customer.lastName}, ${customer.firstName}`}</h1>
<div className="mb-4">
<h2 className="inline font-semibold">Id:</h2> {customer.id}
</div>
<div className="mb-4">
<h2 className="inline font-semibold">Status:</h2> {customer.status}
</div>
<div className="mb-4">
<h2 className="inline font-semibold">Score:</h2> {`${customer.score} (${customer.coefficient})`}
</div>
<div className="mb-4">
<h2 className="inline font-semibold">Email Addresses:</h2>
{customer.emails.length > 0 ? (
<ul className="list-square">
{customer.emails.map(({ email, name }) => {
return (
<Email
key={email}
email={email}
name={name}
/>
);
})}
</ul>
) : (
<p className="ml-4 italic">none</p>
)}
</div>
</main>
);
}
Edit2: Screenshot of errors:
This is the console log showing the hydration errors when using the template literal (right side) versus the console log showing no errors when not using the template literal (left side). The only change between the two is how the string is built as shown in the original post.
These errors only occur when run in a packaged state (either locally in Docker or deployed to GCP). They never occur when running in development mode, so I don't know how to get more clear error logs.
I've run into a hydration issue that's really confusing me. I have a simple component that displays a single list element with an email address and name if there is one.
Here's the original component. It works perfectly when running in dev mode, and displays fine in a deployed state, but throws multiple hydration errors:
export default function Email({ email, name }: EmailProps) {
return (
<li className="ml-6">
{name ? `${name} <${email}>` : email}
</li>
);
}
After some trial and error, I was able to pinpoint the issue was with the template literal. When I remove the template literal, and instead use four separate strings, the hydration errors go away.
export default function Email({ email, name }: EmailProps) {
return (
<li className="ml-6">
{name ? (
<>{name}{" <"}{email}{">"}</>
) : (
email
)}
</li>
);
}
I don't see any reason why the template literal should be causing hydration errors. Any ideas?
Edit: Here's the page.tsx
:
export default async function Details({ params: { customerId } }: DetailsProps) {
unstable_noStore();
const customer = await database.customer.findUnique({
where: { id: customerId },
include: {
emails: true,
},
});
if (!customer) {
return notFound();
}
return (
<main className="p-6">
<h1 className="mb-6 text-2xl">{`${customer.lastName}, ${customer.firstName}`}</h1>
<div className="mb-4">
<h2 className="inline font-semibold">Id:</h2> {customer.id}
</div>
<div className="mb-4">
<h2 className="inline font-semibold">Status:</h2> {customer.status}
</div>
<div className="mb-4">
<h2 className="inline font-semibold">Score:</h2> {`${customer.score} (${customer.coefficient})`}
</div>
<div className="mb-4">
<h2 className="inline font-semibold">Email Addresses:</h2>
{customer.emails.length > 0 ? (
<ul className="list-square">
{customer.emails.map(({ email, name }) => {
return (
<Email
key={email}
email={email}
name={name}
/>
);
})}
</ul>
) : (
<p className="ml-4 italic">none</p>
)}
</div>
</main>
);
}
Edit2: Screenshot of errors:
This is the console log showing the hydration errors when using the template literal (right side) versus the console log showing no errors when not using the template literal (left side). The only change between the two is how the string is built as shown in the original post.
These errors only occur when run in a packaged state (either locally in Docker or deployed to GCP). They never occur when running in development mode, so I don't know how to get more clear error logs.
Share Improve this question edited Jan 30 at 21:29 ddgold asked Jan 29 at 16:16 ddgoldddgold 214 bronze badges 4- 1 Could you please share the outer functionality of your app? It doesnt seem this is the issue – Agustin Moles Commented Jan 29 at 17:26
- Sure, its pretty simple. Its a Next.js app using the app router structure. This page is like a facesheet about a customer, just lists details about them, e.g. ID, status, emails, etc, no dynamic actions. In the page.tsx, there's a database query using Prisma that loads one customer record, and then its just the JSX. Theres no client actions. – ddgold Commented Jan 29 at 21:19
- 1 Please edit the question to add that block of code, you can omit sensitive data if you think there is some, but I think I know where is the problem, just to confirm and give a final answer – Agustin Moles Commented Jan 30 at 0:36
- Add the page.tsx content – ddgold Commented Jan 30 at 1:49
1 Answer
Reset to default 0There seems no issue with your code example, it shouldnt have an hydration error since same data is present/render in both server and client side from what I can see. Nevertheless, I would suggest you following this guide of how setting up a global prisma client, where you dont need to disconnect after querying something. Also from a React standpoint, I dont see a good use case for having the Email
component so far, you could just use the li
element in the page.tsx
file, to avoid prop drilling and so.
Try making these changes and see if it still happens. If so, please share an image of the hydration error instead of your explanation. Hope it helped