I have a state that stores the current app's theme either dark or light. I use useEffect hook to store the state changes in localStorage so that when the user refreshes the page, their preferred theme will get saved and another useEffect hook to update my state during the initial load. However, my state does not update during that initial load.
Things I've tried:
Below are my codes:
const [theme, setTheme] = useState({});
useEffect(() => {
const loadedTheme = JSON.parse(localStorage.getItem(themeStorage))
if (loadedTheme) {
console.log("Initial load")
console.log(loadedTheme);
console.log("Setting theme to localStorage")
Promise.resolve()
.then(() => { setTheme({ dark: loadedTheme.dark }) })
.then(() => { console.log(theme) })
.then(() => changeTheme())
}
}, [])
useEffect(() => {
localStorage.setItem(themeStorage, JSON.stringify(theme))
console.log("Inside useEffect to save changed theme")
console.log(theme)
}, [theme])
function changeTheme() {
console.log("Inside changeTheme function");
console.log(theme);
if (!theme || !theme.dark) { //light turns to dark
for (let vars of Object.keys(varTheme)) {
document.documentElement.style.setProperty(`--${vars}`, varTheme[vars][0]);
}
setTheme({ ...theme, dark: true })
}
else {
for (let vars of Object.keys(varTheme)) { //dark turns to light
document.documentElement.style.setProperty(`--${vars}`, varTheme[vars][1]);
}
setTheme({ ...theme, dark: false })
}
}
It's a bit unclear what you think the issue is, but I suspect that you are seeing/describing theme not updating in the first useEffect hook.
The reason for this is that React state is considered const and immutable. Within the useEffect callback the theme state value is closed over in callback scope and no matter of waiting or Promise chaining will change the state value closed over in scope.
const [theme, setTheme] = useState({}); // <-- initial state
useEffect(() => {
const loadedTheme = JSON.parse(localStorage.getItem(themeStorage))
if (loadedTheme) {
console.log("Initial load")
console.log(loadedTheme);
console.log("Setting theme to localStorage")
Promise.resolve()
.then(() => { setTheme({ dark: loadedTheme.dark }) })
.then(() => { console.log(theme) }) // <-- still initial state
.then(() => changeTheme())
}
}, []); // <-- mounting "instance" of state
You then invoke changeTheme which from the enclosure and it will also sill access the "stale" state of the enclosure.
LocalStorage is synchronous, so you can set your initial state from it with an initializer function.
const loadThemeFromStorage = () => {
const { dark } = JSON.parse(localStorage.getItem(themeStorage)) ?? {};
return { dark };
};
const [theme, setTheme] = useState(loadThemeFromStorage());
Now with the state already initially set you can safely call changeTheme in the mounting useEffect hook.
useEffect(() => {
console.log("Initial load");
console.log(theme);
changeTheme();
}, []);
useEffect(() => {
localStorage.setItem(themeStorage, JSON.stringify(theme));
console.log("Inside useEffect to save changed theme");
console.log(theme);
}, [theme]);