fix: ssr rendering
This commit is contained in:
@@ -19,6 +19,7 @@ export function LocaleSwitcher() {
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2 w-full py-2 hover:opacity-80">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
src={`/flags/${currentLocale}.svg`}
|
||||
alt={currentLocale}
|
||||
|
||||
+55
-37
@@ -1,16 +1,16 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useCallback, useSyncExternalStore } from 'react';
|
||||
|
||||
type Theme = 'light' | 'dark';
|
||||
|
||||
function getCookie(name: string) {
|
||||
if (typeof document === 'undefined') return null;
|
||||
// ── Helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
function getCookie(name: string): string | null {
|
||||
if (typeof document === 'undefined') return null;
|
||||
const value = `; ${document.cookie}`;
|
||||
const parts = value.split(`; ${name}=`);
|
||||
|
||||
if (parts.length === 2) return parts.pop()?.split(';').shift();
|
||||
if (parts.length === 2) return parts.pop()?.split(';').shift() ?? null;
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -18,40 +18,58 @@ function setCookie(value: Theme) {
|
||||
document.cookie = `theme=${value}; path=/; max-age=31536000`;
|
||||
}
|
||||
|
||||
function readThemeFromDOM(): Theme {
|
||||
const cookieTheme = getCookie('theme') as Theme | null;
|
||||
if (cookieTheme === 'light' || cookieTheme === 'dark') return cookieTheme;
|
||||
if (document.documentElement.classList.contains('dark')) return 'dark';
|
||||
return 'light';
|
||||
}
|
||||
|
||||
function applyThemeToDOM(t: Theme) {
|
||||
document.documentElement.classList.toggle('dark', t === 'dark');
|
||||
document.documentElement.classList.toggle('light-mode', t === 'light');
|
||||
}
|
||||
|
||||
// ── External store ────────────────────────────────────────────────────────────
|
||||
// useSyncExternalStore requires a stable subscribe function and a getSnapshot.
|
||||
// We use a simple Set of listeners to notify React when the theme changes.
|
||||
|
||||
const listeners = new Set<() => void>();
|
||||
let clientTheme: Theme | null = null; // lazily initialised on first client read
|
||||
|
||||
function getSnapshot(): Theme {
|
||||
if (clientTheme === null) {
|
||||
clientTheme = readThemeFromDOM();
|
||||
}
|
||||
return clientTheme;
|
||||
}
|
||||
|
||||
// The server always returns the default theme so SSR HTML is deterministic.
|
||||
// The client will then reconcile with the real cookie/DOM value via getSnapshot.
|
||||
function getServerSnapshot(): Theme {
|
||||
return 'dark';
|
||||
}
|
||||
|
||||
function subscribe(callback: () => void): () => void {
|
||||
listeners.add(callback);
|
||||
return () => listeners.delete(callback);
|
||||
}
|
||||
|
||||
function setGlobalTheme(next: Theme) {
|
||||
clientTheme = next;
|
||||
applyThemeToDOM(next);
|
||||
setCookie(next);
|
||||
listeners.forEach((cb) => cb());
|
||||
}
|
||||
|
||||
// ── Hook ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
export function useTheme() {
|
||||
const [theme, setTheme] = useState<Theme>('dark');
|
||||
const theme = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
||||
|
||||
useEffect(() => {
|
||||
const cookieTheme = getCookie('theme') as Theme | null;
|
||||
const rootTheme = document.documentElement.classList.contains('dark')
|
||||
? 'dark'
|
||||
: 'light';
|
||||
|
||||
const initial = cookieTheme || rootTheme || 'dark';
|
||||
|
||||
setTheme(initial);
|
||||
|
||||
document.documentElement.classList.toggle('dark', initial === 'dark');
|
||||
document.documentElement.classList.toggle(
|
||||
'light-mode',
|
||||
initial === 'light',
|
||||
);
|
||||
}, []);
|
||||
|
||||
function applyTheme(t: Theme) {
|
||||
document.documentElement.classList.toggle('dark', t === 'dark');
|
||||
document.documentElement.classList.toggle('light-mode', t === 'light');
|
||||
|
||||
setCookie(t);
|
||||
}
|
||||
|
||||
function toggleTheme() {
|
||||
setTheme((prev) => {
|
||||
const next = prev === 'dark' ? 'light' : 'dark';
|
||||
applyTheme(next);
|
||||
return next;
|
||||
});
|
||||
}
|
||||
const toggleTheme = useCallback(() => {
|
||||
setGlobalTheme(theme === 'dark' ? 'light' : 'dark');
|
||||
}, [theme]);
|
||||
|
||||
return {
|
||||
theme,
|
||||
|
||||
Reference in New Issue
Block a user