skip to Main Content

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>
  )
}

3

Answers


  1. Have you considered using an empty dependency array so that the useEffect hook runs after the components are rendered then updates the state based on the light mode?

    Login or Signup to reply.
  2. A functional component re-renders when:

    1. any props change referential identity
    2. useState’s setState / useReducer’s dispatch is called
    3. useContext value changes referential identity

    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.
    }
    
    Login or Signup to reply.
  3. first, ensure that you have defined two variable properties in the global CSS file based on your theme. then in your most outer parent component, you can set the data attribute to set the current theme as below.

      return (
        <div data-theme={theme === "darkMode" ? "dark-mode-theme" : "light-mode-theme"}>
          ...
        </div>
      )
    

    you should have a mechanism to switch mode and keep the theme as a state variable in most outer parent components. according to this implementation, your theme should change based on the theme change.
    below you can see how you can keep your theme-based color variable in the global.css file.

        body[data-theme="light-mode-theme"] {
      --unnamed-collor-303030: #303030;
      --unnamed-color-565656: #565656;
      --unnamed-color-2f69fe: #2f69fe;
    ....
    }
        body[data-theme="dark-mode-theme"] {
      --unnamed-collor-303030: #303030;
      --unnamed-color-565656: #565656;
      --unnamed-color-2f69fe: #2f69fe;
    ....
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search