Next.js Discord

Discord Forum

Hydration error when adding a dark mode / light mode toggle button component

Answered
Order posted this in #help-forum
Open in Discord
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?

10 Replies

Answer
"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
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?
thanks, you da best
you're welcome