Hydration error when adding a dark mode / light mode toggle button component
Answered
Order posted this in #help-forum
OrderOP
Hello. So I've run into this issue where I'm getting a hydration error that the initial UI doesn't match what was rendered on the server whenever I refresh my page after adding a dark mode button toggle. First, here's how the code of the toggle component looks:
At first I thought it might be because of the lucide icons themselves since the error was : "Expected server HTML to contain a matching <circle> in <svg>."
But then I removed the icons and simply wrote Dark and Light and I got a different error:
"Text content did not match. Server: "Dark" Client: "Light" ".
Seems like whenever my button's state is different on the server and on the client which is something people run into quite often but I don't know what the best way is to solve it. Is my component just poorly written?
"use client";
import { useTheme } from "next-themes";
import { Moon, Sun } from "lucide-react";
import { Button } from "./ui/button";
export default function DarkModeToggle() {
const { theme, setTheme } = useTheme();
return (
<>
{theme === "dark" ? (
<Button
variant="ghost"
onClick={() => {
setTheme("light");
}}
>
<Sun />
</Button>
) : (
<Button
onClick={() => {
setTheme("dark");
}}
>
<Moon />
</Button>
)}
</>
);
}At first I thought it might be because of the lucide icons themselves since the error was : "Expected server HTML to contain a matching <circle> in <svg>."
But then I removed the icons and simply wrote Dark and Light and I got a different error:
"Text content did not match. Server: "Dark" Client: "Light" ".
Seems like whenever my button's state is different on the server and on the client which is something people run into quite often but I don't know what the best way is to solve it. Is my component just poorly written?
Answered by joulev
no,
useTheme is just hydration unsafe. see https://github.com/pacocoursey/next-themes?tab=readme-ov-file#avoid-hydration-mismatch10 Replies
@Order Hello. So I've run into this issue where I'm getting a hydration error that the initial UI doesn't match what was rendered on the server whenever I refresh my page after adding a dark mode button toggle. First, here's how the code of the toggle component looks:
`"use client";
import { useTheme } from "next-themes";
import { Moon, Sun } from "lucide-react";
import { Button } from "./ui/button";
export default function DarkModeToggle() {
const { theme, setTheme } = useTheme();
return (
<>
{theme === "dark" ? (
<Button
variant="ghost"
onClick={() => {
setTheme("light");
}}
>
<Sun />
</Button>
) : (
<Button
onClick={() => {
setTheme("dark");
}}
>
<Moon />
</Button>
)}
</>
);
}
`
At first I thought it might be because of the lucide icons themselves since the error was : "Expected server HTML to contain a matching <circle> in <svg>."
But then I removed the icons and simply wrote Dark and Light and I got a different error:
"Text content did not match. Server: "Dark" Client: "Light" ".
Seems like whenever my button's state is different on the server and on the client which is something people run into quite often but I don't know what the best way is to solve it. Is my component just poorly written?
no,
useTheme is just hydration unsafe. see https://github.com/pacocoursey/next-themes?tab=readme-ov-file#avoid-hydration-mismatchAnswer
OrderOP
"use client";
import { useTheme } from "next-themes";
import { Moon, Sun } from "lucide-react";
import { Button } from "./ui/button";
import { useEffect, useState } from "react";
export default function DarkModeToggle() {
const [mounted, setMounted] = useState(false);
const { theme, setTheme } = useTheme();
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) {
return null;
}
return (
<>
{theme === "dark" ? (
<Button
variant="ghost"
onClick={() => {
setTheme("light");
}}
>
<Sun />
</Button>
) : (
<Button
onClick={() => {
setTheme("dark");
}}
>
<Moon />
</Button>
)}
</>
);
}I think that fixed it, can't see any more hydration errors
yep look good to me, though you may want to display a skeleton button or just a button that does nothing, to prevent layout shift
OrderOP
yeah I'm just playing around, it's not a real project, can I ask one more thing.. so how does the state of the theme save for the user in production? Is it cached in local storage or something
if they log out and log in again will it be saved ?
actually it shouldn't care about login status but it should just remember the browser settings right?
@Order yeah I'm just playing around, it's not a real project, can I ask one more thing.. so how does the state of the theme save for the user in production? Is it cached in local storage or something
yes in local storage and it doesn't care about the authentication state
OrderOP
thanks, you da best
you're welcome