ShadCNUI with NextJS
Unanswered
josh posted this in #help-forum
joshOP
I am currently on the latest version of NextJS. I am trying to wrap a link over a table row component from SHADCN-UI. However I run into alot of hydration issues with the UI rendering incorrectly.
Goal: Make the whole row clickable and navigate to another page.
Code attached as image:
Error messages:
1. Expected server HTML to contain a matching <table> in <div>.
2. Uncaught Error: Hydration failed because the initial UI does not match what was rendered on the server.
3. An error occurred during hydration. The server HTML was replaced with client content in <#document>.
4. validateDOMNesting(...): <tr> cannot appear as a child of <a>.
5. Warning: validateDOMNesting(...): <a> cannot appear as a child of <tbody>.
Goal: Make the whole row clickable and navigate to another page.
Code attached as image:
Error messages:
1. Expected server HTML to contain a matching <table> in <div>.
2. Uncaught Error: Hydration failed because the initial UI does not match what was rendered on the server.
3. An error occurred during hydration. The server HTML was replaced with client content in <#document>.
4. validateDOMNesting(...): <tr> cannot appear as a child of <a>.
5. Warning: validateDOMNesting(...): <a> cannot appear as a child of <tbody>.
68 Replies
Asian black bear
m not sure but u can wrap the links in a div instead of mapping directly in the TableBody
@josh please post the code in a text wrapped in 3 backticks ts ending with 3 backticks
@josh I am currently on the latest version of NextJS. I am trying to wrap a link over a table row component from SHADCN-UI. However I run into alot of hydration issues with the UI rendering incorrectly.
Goal: Make the whole row clickable and navigate to another page.
Code attached as image:
Error messages:
1. Expected server HTML to contain a matching <table> in <div>.
2. Uncaught Error: Hydration failed because the initial UI does not match what was rendered on the server.
3. An error occurred during hydration. The server HTML was replaced with client content in <#document>.
4. validateDOMNesting(...): <tr> cannot appear as a child of <a>.
5. Warning: validateDOMNesting(...): <a> cannot appear as a child of <tbody>.
as for the problem you're facing, I think the issue is inherently in your array, try using [1,2].map instead of headers.map and instruments.map, if the error disappears it means that your arrays are initially empty and the server is rendering them before they are ready and therefore causing a hydration error
joshOP
return (
<>
<div>InstrumentPage</div>
<Table>
<TableCaption>A list of instruments.</TableCaption>
<TableHeader>
<TableRow>
{headers.map((header: HeaderType) => (
<TableHead key={header.name} className={header.width}>
{header.name}
</TableHead>
))}
</TableRow>
</TableHeader>
<TableBody>
{instruments.map((instrument: InstrumentType) => (
<Link
key={instrument.instrumentId}
href={`/instruments/${instrument.instrumentId}`}
>
<TableRow>
<TableCell>{instrument.instrumentId}</TableCell>
<TableCell>{instrument.instrumentName}</TableCell>
</TableRow>
</Link>
))}
</TableBody>
</Table>
</>
);@dank() as for the problem you're facing, I think the issue is inherently in your array, try using [1,2].map instead of headers.map and instruments.map, if the error disappears it means that your arrays are initially empty and the server is rendering them before they are ready and therefore causing a hydration error
joshOP
Hey man, thanks for helping. I posted the code as per your instructions below. What do you mean by [1,2].map i dont quite get it
@Asian black bear m not sure but u can wrap the links in a div instead of mapping directly in the TableBody
joshOP
What do you mean by this? I need the whole row to be clickable.
np
`
hold up
return (
<>
<div>InstrumentPage</div>
<Table>
<TableCaption>A list of instruments.</TableCaption>
<TableHeader>
<TableRow>
{[1,2].map((header: HeaderType) => (
<TableHead key={header.name} className={header.width}>
{header.name}
</TableHead>
))}
</TableRow>
</TableHeader>
<TableBody>
{[1,2].map((instrument: InstrumentType) => (
<Link
key={instrument.instrumentId}
href={`/instruments/${instrument.instrumentId}`}
>
<TableRow>
<TableCell>{instrument.instrumentId}</TableCell>
<TableCell>{instrument.instrumentName}</TableCell>
</TableRow>
</Link>
))}
</TableBody>
</Table>
</>
);try this, does this cause a hydration error?
joshOP
What is the [1,2] supposed to represent? Headers is an array of JSON object, similar to instruments, an array of json objects
@josh What is the [1,2] supposed to represent? Headers is an array of JSON object, similar to instruments, an array of json objects
nothing it's for debugging purposes
joshOP
Hm,, this doesnt even compile though, because header.name and header.width will be undefined?
it will just show nth
we need to see if the arrays you're using are behind the hydration problem
if the error persists then they're not
joshOP
Hmm i see, app-index.js:31 Warning: Each child in a list should have a unique "key" prop
I only get the above when i replace as per your suggestion
I only get the above when i replace as per your suggestion
okay that's fine
joshOP
but the headers and instruments array are static arrays, i am not fetching any data from the backend now. (i.e i hardcoded the values to initially cause the backend is not up yet)
you need to listen to when your component mounts
1 sec
joshOP
const InstrumentsPage = () => {
return (
<>
<div>InstrumentPage</div>
<Table>
<TableCaption>A list of instruments.</TableCaption>
<TableHeader>
<TableRow>
{headers.map((header: HeaderType) => (
<TableHead key={header.name} className={header.width}>
{header.name}
</TableHead>
))}
</TableRow>
</TableHeader>
<TableBody>
{instruments.map((instrument: InstrumentType) => (
<Link
key={instrument.instrumentId}
href={`/instruments/${instrument.instrumentId}`}
>
<TableRow>
<TableCell>{instrument.instrumentId}</TableCell>
<TableCell>{instrument.instrumentName}</TableCell>
<TableCell>{instrument.instrumentType}</TableCell>
</TableRow>
</Link>
))}
</TableBody>
</Table>
</>
);
};For example, my headers is just a const:
export const headers = [
{ name: 'InstrumentId', className: 'w-[100px]' },
{ name: 'Instrument Name' },
{ name: 'Instrument Type' },
{ name: 'Currency' },
{ name: 'IsinCode' },
{ name: 'SedolCode' },
{ name: 'Symbol' },
{ name: 'Country' },
{ name: 'Sector' },
{ name: 'createdAt' },
{ name: 'modifiedAt' },
];create a hook to listen to when the component mounts
add this to your /hooks
add this to your /hooks
import { useEffect, useState } from "react";
export const useMounted = () => {
const [mounted, setMounted] = useState<boolean>(false);
useEffect(() => {
setMounted(true);
return () => {
setMounted(false);
};
}, []);
return mounted;
};import {useMounted} from "@_lib/hooks/useMounted"
const InstrumentsPage = () => {
const isMounted = useMounted();
isMounted?return (
<>
<div>InstrumentPage</div>
<Table>
<TableCaption>A list of instruments.</TableCaption>
<TableHeader>
<TableRow>
{headers.map((header: HeaderType) => (
<TableHead key={header.name} className={header.width}>
{header.name}
</TableHead>
))}
</TableRow>
</TableHeader>
<TableBody>
{instruments.map((instrument: InstrumentType) => (
<Link
key={instrument.instrumentId}
href={`/instruments/${instrument.instrumentId}`}
legacyBehavior={true}
>
<TableRow>
<TableCell>{instrument.instrumentId}</TableCell>
<TableCell>{instrument.instrumentName}</TableCell>
<TableCell>{instrument.instrumentType}</TableCell>
<TableCell>{instrument.currency}</TableCell>
<TableCell>{instrument.isinCode}</TableCell>
<TableCell>{instrument.sedolCode}</TableCell>
<TableCell>{instrument.symbol}</TableCell>
<TableCell>{instrument.country}</TableCell>
<TableCell>{instrument.sector}</TableCell>
<TableCell>{instrument.createdAt}</TableCell>
<TableCell>{instrument.modifiedAt}</TableCell>
</TableRow>
</Link>
))}
</TableBody>
</Table>
</>
):null
};try doing that
joshOP
i would have to change this to a client component right?
legacyBehavior={true} -> by the way this is false
yeah you would
joshOP
Warning: validateDOMNesting(...): <tr> cannot appear as a child of <a>.
It stiull throws the same error
I need to set useLegacyBehavior to true, only then it solves the problem
but when i do that, hovering over the row does not show the redirect link
I think that's one the nextjs hydration rules where a Link <a> tag cannot be a parent of a TableRow <tr>
try to put the link inside a table cell
to see if that's the actual problem
joshOP
ah okay, but if i do that, i have to specifically click that cell, then i can navigate
instead of being able to do so by clicking the whole row
right, do it for now to make sure that it is indeed the case of <tr> can't be a child of <a>
joshOP
<td> cannot appear as a child of <a>.
Well it seems that it wont work regardless
I really cant seem to see the motivation behind why <td> <tr> cannot appear as a child for <a> :/ its causing so many issues
it might not be the case, can you show me the code with the edits I suggested earlier
joshOP
const InstrumentsPage = () => {
const isMounted = useMounted();
return isMounted ? (
<>
<div>InstrumentPage</div>
<Table>
<TableCaption>A list of instruments.</TableCaption>
<TableHeader>
<TableRow>
{headers.map((header: HeaderType) => (
<TableHead key={header.name} className={header.width}>
{header.name}
</TableHead>
))}
</TableRow>
</TableHeader>
<TableBody>
{instruments.map((instrument: InstrumentType) => (
<TableRow key={instrument.instrumentId}>
<Link
key={instrument.instrumentId}
href={`/instruments/${instrument.instrumentId}`}
>
<TableCell>{instrument.instrumentId}</TableCell>
</Link>
<TableCell>{instrument.instrumentName}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</>
) : null;
};
export default InstrumentsPage;const InstrumentsPage = () => {
const isMounted = useMounted();
return isMounted ? (
<>
<div>InstrumentPage</div>
<Table>
<TableCaption>A list of instruments.</TableCaption>
<TableHeader>
<TableRow>
{headers.map((header: HeaderType) => (
<TableHead key={header.name} className={header.width}>
{header.name}
</TableHead>
))}
</TableRow>
</TableHeader>
<TableBody>
{instruments.map((instrument: InstrumentType) => (
<TableRow key={instrument.instrumentId}>
<Link
key={instrument.instrumentId}
href={`/instruments/${instrument.instrumentId}`}
>
<TableCell>{instrument.instrumentId}</TableCell>
</Link>
<TableCell>{instrument.instrumentName}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</>
) : null;
};
export default InstrumentsPage;btw like this
so removing the array gets rid of the error right?
did you add the "use client" directive
joshOP
yeap i did
your code still gives a hydration error but a differetn oen
<td> cannot appear as a child of <a>.
what have you tried to fix it
joshOP
so far it seems that using legacyBehavior={true}fixes it
the rest, its not possible.
idk I can only do so much from here,
joshOP
appreciate your help though
seems that its just a limitation with nextjs
It's not the Link tho I have something like that working
joshOP
what UI library are u using?
<Link href={props.url} style={{ textDecoration: "none" }}>
<Card
sx={{
maxWidth: { xs: "70%", md: "90%" },
maxHeight: { md: 200 },
margin: 2,
display: { md: "flex", xs: "block" },
}}
variant="outlined"
>
stuff
</Card>
</Link>
);MUI
joshOP
I guess the design of the UI library matters. <a> is inside the link component. and i cant wrap <tr>/<td> inside the <a> component
if you placed the link component on it's own do you still get the error?
const InstrumentsPage = () => {
const isMounted = useMounted();
return isMounted ? (
<>
<Link
key={instrument.instrumentId}
href={`/instruments/${instrument.instrumentId}`}
>
</Link>
</>
) : null;
};
export default InstrumentsPage;like so
btw you should keep a copy of your original file so you can revert any changes you make
at the end of debugging it