I was configuring the context in Tailwind for a Next.js 14 website, and I just wanted to provide a global theme configuration for my project. So, I created the ThemeContext and added the ‘use client’ at the top of the file. However, it still renders on the server side and gives an error.
⨯ srccontextsThemeContext.tsx (22:28) @ localStorage
⨯ ReferenceError: localStorage is not defined
at eval (./src/contexts/ThemeContext.tsx:20:29)
at ThemeProvider (./src/contexts/ThemeContext.tsx:19:78)
20 | export const ThemeProvider: React.FC<ThemeProviderProps> = ({ children }) => {
21 | const [theme, setTheme] = useState<Theme>(() => {
> 22 | const storedTheme = localStorage?.getItem('theme');
| ^
23 | return storedTheme === Theme.DARK ? Theme.DARK : Theme.LIGHT;
24 | });
25 |
✓ Comp
I created a mounted state to solve the problem, but I want to understand why this is happening.
'use client';
...
export const ThemeProvider: React.FC<ThemeProviderProps> = ({ children }) => {
const [theme, setTheme] = useState<Theme>(Theme.LIGHT);
const [mounted, setMounted] = useState(false);
useEffect(() => {
if (mounted) {
return
}
const storedTheme = localStorage?.getItem('theme');
console.log("storedTheme", storedTheme)
setTheme(storedTheme === Theme.DARK ? Theme.DARK : Theme.LIGHT);
setMounted(true)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
if (!mounted) {
return
}
if (theme === Theme.DARK) {
document.documentElement.classList.add('dark');
localStorage?.setItem('theme', Theme.DARK);
} else {
document.documentElement.classList.remove('dark');
localStorage?.setItem('theme', Theme.LIGHT);
}
}, [theme, mounted])
2
Answers
I think you need to update your useEffect in ThemeProvider file. Please check below updated code:-
Let me know if it works for you or not.
This happens because the component was not yet mounted and you had already called the
localstorage
object, thelocalstorage
object is specific to the browser and we access it via thewindow
object (window.localstorage
), and to access this object you must be sure that the component is already mounted.This is where the
useEffect
comes in,useEffect
is called immediately after the first rendering, so this is the best place to call thelocalstorage
object because we are sure at this level that thewindow
object exists.There is no need to check if the component is mounted in
useEffect
, it will always be the case.