So I'm using nextjs and I'm trying to build a Daisyui theme switcher. It generally works just fine, but there are some kinks I don't quite understand.
I have a simple theme switch button:
// Themeswitch.jsx
'use client';
import { useContext } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faMoon, faSun } from '@fortawesome/free-solid-svg-icons';
import { ThemeContext } from 'context/SiteTheme';
const ThemeSwitch = function () {
const { theme, setTheme, choices, darkMode } = useContext(ThemeContext);
return (
<button
type="button"
className="btn btn-ghost"
onClick={() => {
setTheme(theme === choices[0] ? choices[1] : choices[0]);
}}
aria-label="switch theme between light and dark"
>
<FontAwesomeIcon icon={darkMode ? faSun : faMoon} />
</button>
);
};
export default ThemeSwitch;
As you can see, it's a client component. It still gets rendered on the server side, and If I log out the theme and dark mode, then on the server side, it logs out as "dim" and true and "emerald" and false on the client side, as it picks up the theme name from the local storage. But the thing is - the icon does not switch from "sun" to "moon" when rendering on the client side. Why?
And on a related note, this is the context provider looks like this:
'use client';
import { createContext, useMemo, useState, useEffect } from 'react';
export const ThemeContext = createContext();
const isLocalStorageEnabled = () => {
try {
const key = `__storage__test`;
window.localStorage.setItem(key, null);
window.localStorage.removeItem(key);
return true;
} catch (e) {
return false;
}
};
export const ThemeContextProvider = function ({ children, defaultTheme, choices }) {
// const localTheme = isLocalStorageEnabled() && localStorage.theme;
const localTheme = localStorage.theme;
let baseTheme;
if (choices.indexOf(localTheme) > -1) {
baseTheme = localTheme;
} else {
baseTheme = defaultTheme;
}
const [theme, setTheme] = useState(baseTheme); // Replace null with your initial theme state
useEffect(() => {
document.documentElement.setAttribute('data-theme', theme);
localStorage.theme = theme;
}, [theme]);
const memoizedContext = useMemo(
() => ({ theme, setTheme, choices, darkMode: theme === defaultTheme }),
[choices, defaultTheme, theme],
);
return <ThemeContext.Provider value={memoizedContext}>{children}</ThemeContext.Provider>;
};
If I run the code like this, then localStorage.theme generates an error on the server side, obviously, as it's not available. However, having it generate an error on the server side is the only way to ensure that the theme does not visibly switch/flicker when rendered on the client side. If I comment in the line const localTheme = isLocalStorageEnabled() && localStorage.theme;, then there's no error on the server side, but then the page first renders as darktheme, as that's default and then switches to the light theme ( if it's turned on) about 0.5 seconds later. Also, if I have an error on the server side, then the icon also renders correctly.
Is there a way to do that better?