-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Fix hydration error #643
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Fix hydration error #643
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| "use client" | ||
|
|
||
| import { ThemeProvider, ThemeProviderProps } from "next-themes"; | ||
| import { useEffect, useState } from "react" | ||
|
|
||
| interface CustomThemeProviderProps extends ThemeProviderProps { | ||
| children: React.ReactNode; | ||
| } | ||
|
|
||
| export default function CustomThemeProvider({ children, ...props }: CustomThemeProviderProps) { | ||
| const [mounted, setMounted] = useState(false); | ||
| useEffect(() => { | ||
| setMounted(true); | ||
| }, []); | ||
|
|
||
| if (!mounted) { | ||
| return <>{children}</>; | ||
| } | ||
|
|
||
| return <ThemeProvider {...props}>{children}</ThemeProvider > | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,8 +18,12 @@ export function ThemeToggle({ className }: ThemeToggleProps) { | |
| className="h-7" | ||
| onClick={() => setTheme(theme === "dark" ? "light" : "dark")} | ||
| > | ||
| <Sun className="!size-[1.1rem]" /> | ||
| {theme === "dark" ? ( | ||
| <Sun className="!size-[1.1rem]" /> | ||
| ) : (<Moon className="!size-[1.1rem]" />)} | ||
|
Comment on lines
+21
to
+23
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chainPotential hydration mismatch with undefined theme. The Consider guarding against Apply this diff to add a guard: - {theme === "dark" ? (
+ {theme === "dark" ? (
<Sun className="!size-[1.1rem]" />
- ) : (<Moon className="!size-[1.1rem]" />)}
+ ) : theme === "light" ? (
+ <Moon className="!size-[1.1rem]" />
+ ) : (
+ <Sun className="!size-[1.1rem]" />
+ )}Alternatively, consider not rendering any icon until - {theme === "dark" ? (
- <Sun className="!size-[1.1rem]" />
- ) : (<Moon className="!size-[1.1rem]" />)}
+ {theme === "dark" ? (
+ <Sun className="!size-[1.1rem]" />
+ ) : theme === "light" ? (
+ <Moon className="!size-[1.1rem]" />
+ ) : null}Run the following script to check if the CustomThemeProvider properly prevents hydration mismatches: 🏁 Script executed: #!/bin/bash
# Description: Check CustomThemeProvider implementation and ThemeToggle usage
# Check CustomThemeProvider implementation for mounted state
echo "=== CustomThemeProvider implementation ==="
rg -A 20 'export.*CustomThemeProvider' --type tsx
# Check if ThemeToggle guards against undefined theme
echo -e "\n=== ThemeToggle usage of theme ==="
rg -B 2 -A 2 'theme ===' apps/web/src/components/theme-toggle.tsxLength of output: 448 🏁 Script executed: #!/bin/bash
# Better search for CustomThemeProvider without problematic type flag
echo "=== CustomThemeProvider implementation ==="
rg -A 20 'export.*CustomThemeProvider'
# Also check the full theme-toggle.tsx file to see all code
echo -e "\n=== Full theme-toggle.tsx file ==="
cat apps/web/src/components/theme-toggle.tsx
# Check if there's a hooks file or provider file
echo -e "\n=== Looking for theme-related files ==="
fd -e tsx -e ts | grep -i theme | head -20Length of output: 1736 Address potential hydration mismatch in conditional theme rendering. The component renders different icons based on the Add a guard to handle the - {theme === "dark" ? (
+ {theme === "dark" ? (
<Sun className="!size-[1.1rem]" />
- ) : (<Moon className="!size-[1.1rem]" />)}
+ ) : theme === "light" ? (
+ <Moon className="!size-[1.1rem]" />
+ ) : null}Or ensure the component doesn't render until 🤖 Prompt for AI Agents |
||
| <span className="sr-only">{theme === "dark" ? "Light" : "Dark"}</span> | ||
| </Button> | ||
| ); | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Missing ThemeProvider context causes runtime errors for child components.
Returning
<>{children}</>without theThemeProviderwrapper when!mounted(lines 16-18) breaks the theme context. Components likeToaster(which callsuseTheme()as shown in the relevant snippet fromsonner.tsx) will throw an error during the initial render: "useTheme must be used within a ThemeProvider".The fix for hydration errors should keep the
ThemeProviderwrapped at all times. ThesuppressHydrationWarningattribute on the<html>tag (already present inlayout.tsxline 33) handles theme-related hydration mismatches.Apply this diff to fix the context issue:
export default function CustomThemeProvider({ children, ...props }: CustomThemeProviderProps) { const [mounted, setMounted] = useState(false); useEffect(() => { setMounted(true); }, []); - if (!mounted) { - return <>{children}</>; - } - - return <ThemeProvider {...props}>{children}</ThemeProvider > + return ( + <ThemeProvider {...props}> + {mounted ? children : null} + </ThemeProvider> + ); }Alternatively, if you want to avoid the layout shift from returning
null, simply always render children within ThemeProvider and rely onsuppressHydrationWarning:export default function CustomThemeProvider({ children, ...props }: CustomThemeProviderProps) { - const [mounted, setMounted] = useState(false); - useEffect(() => { - setMounted(true); - }, []); - - if (!mounted) { - return <>{children}</>; - } - - return <ThemeProvider {...props}>{children}</ThemeProvider > + return <ThemeProvider {...props}>{children}</ThemeProvider>; }The second approach is simpler and leverages Next.js's built-in hydration handling via
suppressHydrationWarning.🤖 Prompt for AI Agents