Reputation: 3
I have created a custom hook that dynamically detects and returns the system color theme. The custom hook correctly detects every change and sets the value accordingly. But the component that uses the custom hook always shows the initial value returned by the hook although it re-renders on every theme change.
I would really appreciate if someone can explain why this happens, and can suggest a suitable solution.
Thanks in advance.
useThemeDetector.js
import { useState, useEffect } from 'react';
const useThemeDetector = () => {
// media query
const mq = window.matchMedia("(prefers-color-scheme: dark)");
const [ theme, setTheme ] = useState(mq.matches ? 'dark' : 'light');
const themeListener = e => {
setTheme(
e.matches
? 'dark'
: 'light'
);
};
useEffect(() => {
mq.addListener(themeListener);
return () => { mq.removeListener(themeListener); };
}, [theme]);
// debug output, shows correct value
console.log(`theme: ${theme}, from hook`);
return theme;
};
export default useThemeDetector;
App.js
import Board from './components/Board';
import { useState } from 'react';
import { ThemeContext } from './Context';
import useThemeDetector from './customHooks/useThemeDetector';
const themes = {
'light': {
'bgColor': "#fff",
'fgColor': "#000"
},
'dark': {
'bgColor': "#282c34",
'fgColor': "#61dafb"
}
};
function App() {
const sysTheme = useThemeDetector();
const [ theme, setTheme ] = useState(sysTheme);
const [ bgColor, setBgColor ] = useState(themes[theme]['bgColor']);
const [ fgColor, setFgColor ] = useState(themes[theme]['fgColor']);
// debug output, shows initial value on every render
console.log(`theme: ${theme}, from App`);
const toogleTheme = () => {
if (theme === 'light') {
setTheme('dark');
setBgColor(themes['dark']['bgColor']);
setFgColor(themes['dark']['fgColor']);
}
else {
setTheme('light');
setBgColor(themes['light']['bgColor']);
setFgColor(themes['light']['fgColor']);
}
};
const style = {
// styles...
};
return (
<ThemeContext.Provider value={{ theme, toogleTheme }}>
<div
className="App"
style={style}
>
<Board />
</div>
</ThemeContext.Provider>
);
}
export default App;
Upvotes: 0
Views: 3562
Reputation: 3841
The problem is you are effectively using useState
twice. There is already a state variable inside the custom hook:
const [ theme, setTheme ] = useState(mq.matches ? 'dark' : 'light');
But then in App.js
you are adding another, distinct, state variable:
const [ theme, setTheme ] = useState(sysTheme);
That second variable is different to the one inside the custom hook, and so it just gets initialised to whatever the value of sysTheme
was when it was initialised.
Instead, you can do something like this:
const [ theme, setTheme ] = useState(sysTheme);
[theme, setTheme]
tupleApp.js
do const [theme, setTheme] = useThemeDetector();
...and leave the rest of the code as it is as it is referring to those names.There may be other issues though with your custom hook. Looks like it will add more and more event listeners each time the theme
value changes. You should probably only add an event listener if there is none already there. I'm not 100% sure about this, but I would certainly test it if I were you.
UPDATE: I believe you should make your useEffect
hook depend on setTheme
and not theme
.
Upvotes: 2