I'm currently working in a weather forecast website and I'm trying to apply a theme toggler. I have already set up the functionality, but the chosen theme does not persist when the page is refreshed. Could anyone point out what I'm missing? The console.log shows that the theme inside localStorage changes when I click the theme toggler.
import { WeatherApi } from "./pages/home/weather-api";
import '../src/App.scss'
import { createContext, useEffect, useState } from "react";
import ReactSwitch from "react-switch";
export const ThemeContext = createContext({})
function App() {
const [theme, setTheme] = useState<string>(localStorage.getItem('theme') || 'light')
useEffect(() => {
const localTheme = localStorage.getItem(theme)
console.log(localTheme)
if (!localTheme) {
setTheme("light")
}
if (localTheme) {
if (localTheme === 'light') {
setTheme('light')
localStorage.setItem('theme', theme)
console.log(localStorage)
}
if (localTheme === 'dark') {
setTheme('dark')
localStorage.setItem('theme', theme)
console.log(localStorage)
}
}
},[theme])
const toggleTheme = () => {
setTheme((currentTheme: any) => (currentTheme === "light" ? "dark" : "light"))
}
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
<div className="App" id={theme}>
<WeatherApi />
<div className="switch">
<label>{theme === "light" ? "Light mode" : "Dark mode"}</label>
<ReactSwitch onChange={toggleTheme} checked={theme === "dark"} />
</div>
</div>
</ThemeContext.Provider>
)
}
export default App;
CodePudding user response:
You already load the theme from localStorage
as the default value of the state. The other load from localStorage
in useEffect
is probably discarding/overriding user changes.
Try removing it. For example:
useEffect(() => {
localStorage.setItem('theme', theme);
}, [theme]);
CodePudding user response:
You should load the theme from localStorage and use it as initialState, something like this:
///... more js code
const intialState = localStorage.getItem('theme') || 'light');
const [theme, setTheme] = useState<string>('')
const toggleTheme = () => {
const newTheme = theme === "light" ? "dark" : "light";
localStorage.setItem('theme', newTheme);
setTheme(newTheme);
}
///... more js code
Note: you don't need to use JSON.stringify or JSON.parse in strings
CodePudding user response:
You've got a classic effect hook infinite loop here. The effect hook has a dependency on theme
. If you call setTheme
inside the hook, it will re-run ad infinitum.
I would recommend creating a theme context provider component like this. Since you're using Typescript, you should avoid any
and properly type all your values.
import { createContext, useState, useEffect, FC } from "react";
type Theme = "light" | "dark";
interface ThemeContextValue {
theme: Theme;
toggleTheme: () => void;
}
export const ThemeContext = createContext<ThemeContextValue>({
theme: "light",
toggleTheme: () => {},
});
export const ThemeContextProvider: FC = ({ children }) => {
// initialise state from localStorage
const [theme, setTheme] = useState<Theme>(
(localStorage.getItem("theme") ?? "light") as Theme
);
// update localStorage when the theme changes
useEffect(() => {
localStorage.setItem("theme", theme);
}, [theme]);
const toggleTheme = () => {
setTheme((current) => (current === "light" ? "dark" : "light"));
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
};