Hydration failed because the initial UI
Answered
Txie posted this in #help-forum
TxieOP
In my code I have this sidebar item which uses shadcn/ui Hovercard component with the link router and I'm getting this hydration error.
import React, { useEffect, useState } from "react";
import { usePathname } from "next/navigation";
import Link from "next/link";
import { Icon } from "@/components/ui/Icon";
import {
HoverCard,
HoverCardContent,
HoverCardTrigger,
} from "@/components/ui/hover-card";
interface SidebarItemProps {
parent: string;
title: string;
icon: any;
}
const SideBarItems: React.FC<SidebarItemProps> = ({
parent,
title,
icon,
}) => {
const [isActive, setIsActive] = useState(false);
const pathname = usePathname();
const section = pathname.split("/")[3];
useEffect(() => {
setIsActive(section === parent);
}, [section, parent]);
const fillColor = isActive ? "stroke-accent-500" : "stroke-[rgba(8,191,8,0.5)]";
return (
<HoverCard openDelay={0} closeDelay={0}>
<Link href={`/client/dashboard/${parent}`}>
<HoverCardTrigger>
<div
className={`${isActive ? "bg-static8" : ""} group flex items-center p-2 rounded-lg hover:bg-accent`}
>
<Icon name={icon} className={`w-6 h-6 ${fillColor} group-hover:stroke-accent-500`} strokeWidth={2} />
</div>
</HoverCardTrigger>
</Link>
<HoverCardContent
side="right"
className="bg-background-500 border-none shadow-none p-0 w-auto"
>
<div className="bg-static11 border border-static11 py-px px-3 rounded-lg">
<p className="font-semibold text-base text-white laptop:text-sm group-hover:text-text-50">
{title}
</p>
</div>
</HoverCardContent>
</HoverCard>
);
};
export default SideBarItems;65 Replies
TxieOP
I was sort of able to resolve this by adding
But I feel like there a better way of going about this issue.
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) {
return null;
}
if (!mounted) {
return null;
}But I feel like there a better way of going about this issue.
Unless im suppose to use mount as a skeleton for now until the ui and link loads
American Chinchilla
Do you have "use client" at the top of the file?
TxieOP
yes
"use client";
import React, { useEffect, useState } from "react";
import { usePathname } from "next/navigation";
import Link from "next/link";
interface SidebarItemProps {
parent: string;
title: string;
icon: any;
}
const SideBarItems: React.FC<SidebarItemProps> = ({ parent, title, icon }) => {
const [isActive, setIsActive] = useState(false);
const [mounted, setMounted] = useState(false);
const pathname = usePathname();
const section = pathname.split("/")[3];
useEffect(() => {
setIsActive(section === parent);
}, [section, parent]);
useEffect(() => {
setMounted(true);
}, []);
const fillColor = isActive
? "stroke-accent-500"
: "stroke-[rgba(8,191,8,0.5)]";
if (!mounted) {
return (
<div
className={`${isActive ? "bg-static8" : ""} group flex items-center p-2 rounded-lg hover:bg-accent`}
>
<Icon
name={icon}
className={`w-6 h-6 ${fillColor} group-hover:stroke-accent-500`}
strokeWidth={2}
/>
</div>
);
}
return (
<HoverCard openDelay={0} closeDelay={0}>
<Link href={`/client/dashboard/${parent}`}>
<HoverCardTrigger>
<div
className={`${isActive ? "bg-static8" : ""} group flex items-center p-2 rounded-lg hover:bg-accent`}
>
<Icon
name={icon}
className={`w-6 h-6 ${fillColor} group-hover:stroke-accent-500`}
strokeWidth={2}
/>
</div>
</HoverCardTrigger>
</Link>
<HoverCardContent
side="right"
className="bg-background-500 border-none shadow-none p-0 w-auto"
>
<div className="bg-static11 border border-static11 py-px px-3 rounded-lg">
<p className="font-semibold text-base text-white laptop:text-sm group-hover:text-text-50">
{title}
</p>
</div>
</HoverCardContent>
</HoverCard>
);
};
export default SideBarItems;@American Chinchilla Do you have "use client" at the top of the file?
TxieOP
I pasted the working code that works now but is this the best way of going about this issue?
American Chinchilla
Is there a reason you're using state to keep track of the active status?
right now the url is client/dashboard/home Which toggles the useState for the highlight of section
Feel free to be harsh on my code would rather have someone telling me there a better way of doing
American Chinchilla
I'd just verify equality in the render rather than trying to do it in a state variable
I'm on my phone rn so it's a bit difficult to demo
like verify equality what you mean by that?
American Chinchilla
In your original code try getting rid of the isActive state
And the useeffect
American Chinchilla
Then in the div where you're using the isActive Boolean rn check if section === parent
TxieOP
like this
const isActive = section === parent;American Chinchilla
Yeah that works
I'm thinking the change in pathname will already cause another render which leads to the hydration error
TxieOP
So should I try removing the path name and put a fixed variable for path and see if there still a hydration error?
American Chinchilla
Nah keep the usePathname
Going to need that
TxieOP
ya just to see if there was a hydration issue
American Chinchilla
I think the usePathname combined with trying to use the useeffect before was causing the hydration error
TxieOP
oo yeah i can see that
because useState and rendering and something different when it renders idk the exact reason but something to do with that
American Chinchilla
Oh wait you have a link in a link
That'd do it
TxieOP
What do you mean link in a link?
@Txie I was sort of able to resolve this by adding
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) {
return null;
}
if (!mounted) {
return null;
}
But I feel like there a better way of going about this issue.
Dwarf Crocodile
What’s wrong with this approach?
@Dwarf Crocodile What’s wrong with this approach?
TxieOP
Nothing just don't know if this is like the idle way or there a better way
@Dwarf Crocodile What’s wrong with this approach?
American Chinchilla
Technically nothing, it's just possible to do something similar without that logic
I've never had to do that for a sidebar
TxieOP
What do you do for your sidebars?
American Chinchilla
Approach I described earlier--no state management required, just need to use usePathname and see if the url it takes you to matches
Dwarf Crocodile
I’ve had to do this for react-countdown and something like supressHydrationWarning for next-themes
I’m curious how to properly do it without that logic though!
I’m curious how to properly do it without that logic though!
@Dwarf Crocodile I’ve had to do this for react-countdown and something like supressHydrationWarning for next-themes
I’m curious how to properly do it without that logic though!
American Chinchilla
Be like that for dependencies sometimes
TxieOP
"use client";
import React, { useEffect, useState } from "react";
import { usePathname } from "next/navigation";
import Link from "next/link";
interface SidebarItemProps {
parent: string;
title: string;
icon: any;
}
const SideBarItems: React.FC<SidebarItemProps> = ({ parent, title, icon }) => {
const [mounted, setMounted] = useState(false);
const pathname = usePathname();
const section = pathname.split("/")[3];
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) {
return (
<div
className={`${section === parent ? "bg-static8" : ""} group flex items-center p-2 rounded-lg hover:bg-accent`}
>
<Icon
name={icon}
className={`w-6 h-6 ${section === parent ? "stroke-accent-500" : "stroke-[rgba(8,191,8,0.5)]"} group-hover:stroke-accent-500`}
strokeWidth={2}
/>
</div>
);
}
return (
<HoverCard openDelay={0} closeDelay={0}>
<Link href={`/client/dashboard/${parent}`}>
<HoverCardTrigger>
<div
className={`${section === parent ? "bg-static8" : ""} group flex items-center p-2 rounded-lg hover:bg-accent`}
>
<Icon
name={icon}
className={`w-6 h-6 ${section === parent ? "stroke-accent-500" : "stroke-[rgba(8,191,8,0.5)]"} group-hover:stroke-accent-500`}
strokeWidth={2}
/>
</div>
</HoverCardTrigger>
</Link>
<HoverCardContent
side="right"
className="bg-background-500 border-none shadow-none p-0 w-auto"
>
<div className="bg-static11 border border-static11 py-px px-3 rounded-lg">
<p className="font-semibold text-base text-white laptop:text-sm group-hover:text-text-50">
{title}
</p>
</div>
</HoverCardContent>
</HoverCard>
);
};
export default SideBarItems;This code here works
Dwarf Crocodile
I was just curious about the approach drewb is mentioning as opposed to your original solution
@American Chinchilla Technically nothing, it's just possible to do something similar without that logic
TxieOP
Also thanks for that tip for the useState for changing the pathname for actives one
American Chinchilla
Wait yeah I think you might be link in a link-ing
Instead of wrapping the entire hover card in a link, just put a link in the HoverCardTrigger that wraps the div
American Chinchilla
Answer
American Chinchilla
And then give the hovercardtrigger the asChild prop
American Chinchilla
Yes
TxieOP
o that did work
American Chinchilla
Sick
TxieOP
Thank you
also what does the asChild do for react?
American Chinchilla
In the context of radix/shadcn
So you avoid link in a link that way
TxieOP
Thank you so much for that information!