I have a top-level component that uses a library to determine whether a user's browser is in light or dark mode. It's then being used to set the theme for the app, which include HTML Canvas components (this is important because those do not act like normal React components). Currently, the canvas components do not show the correct theme without a re-render (either a page reload or a hot reload in dev mode).
How do I get the App
component to re-render without a reload? For example, in local dev mode, just the component re-renders when I hit save, and this is the exact behavior I'd like to replicate in the app.
According to other answers on SO, useReducer
is the correct way to force a re-render in a functional component, but the following does not work for me:
function App(props) {
// this is not working
const [, forceUpdate] = useReducer((x: number) => x + 1, 0);
useEffect(() => {
setDarkMode(getColorTheme() === "dark");
// this is not working
forceUpdate();
}, [getColorTheme()]);
}
return (
<div
className={(darkMode ? "dark-mode-theme" : "")}
>
...
</div>
)
}
A functional component re-renders when:
Note: setState will abort the rerender if the next state is the same (referentially speaking) as the previous state.
dispatch also has this behavior for when the resultant of the reducer returns the same state as previous.
Looking at your code, the useEffect is not evaluating when theme changes because App is not rerendering. With React DevTools, you can set the option to make components flash when they rerender. Does the App component flash when theme is changed? This would explain why your code is not working.
I suggest the following hook to solve our specific problem.
/**
* subscribes to colorTheme changes on mount.
* unsubscribes to colorTheme changes on unmount.
* @returns colorTheme
**/
function useColorTheme(): ColorTheme {
const [colorTheme, setColorTheme] = useState<ColorTheme>(getColorTheme);
useEffect(() => {
return onColorThemeChanged(({ newColorTheme }) => setColorTheme(newColorTheme);
}, []);
return colorTheme;
}
function App() {
const colorTheme = useColorTheme(); // rerenders on colorTheme change.
}