useState not updating all components of my app

Heyo. Working with react and I’ve encountered a puzzling issue.

import { useState } from "react";

function useTheme() {
  const [themeName, setThemeName] = useState("dark");
  return [themeName, setThemeName];
}

function Other() {
  const [themeName, setThemeName] = useTheme();
  return <div>{themeName}</div>;
}

function ThemeSwitcher() {
  const [themeName, setThemeName] = useTheme();
  return (
    <>
      <button
        onClick={() => {
          themeName == "dark" ? setThemeName("light") : setThemeName("dark");
        }}
      >
        {"BUTTON"}
      </button>
      {themeName}
    </>
  );
}

function App() {
  const [themeName, setPalette] = useTheme();
  return (
    <div>
      <ThemeSwitcher />
      <Other />
      <div>{themeName}</div>
    </div>
  );
}

export default App;

This is an extremely basic version of my issue, I’ve removed everything I could think of and still it persists. I have a an app with a hook called useTheme, that provides a function that changes the name of a state variable "themeName".

I have a button component ThemeSwitcher that, on click, switches the name of the theme between "dark" and "light".

When clicking the button, the themeName state variable updates (which I know for sure, as the {themeName} fragment inside ThemeSwitcher properly updates). However, the {themeName} inside App and the {themeName} inside Other do not actually update. I fail to see the difference here, and I’m currently baffled as to why one would update and not the others.

Initial state

State after one click.

Any idea why the App component isn’t rerendered despite the state variable changing?

>Solution :

They’re completely separate states. This:

function useTheme() {
  const [themeName, setThemeName] = useState("dark");
  return [themeName, setThemeName];
}

is effectively no different from using useState itself. So your issue is similar to asking why, here:

function Other() {
  const [themeName, setThemeName] = useState();
  return <div>{themeName}</div>;
}

function ThemeSwitcher() {
  const [themeName, setThemeName] = useState();
  // ...

the two themeNames are not in sync – it’s because they have no connection to each other.

To fix this, there are usually 2 approaches that can be used:

  • Declare the theme state in one component, the topmost component such that all descendants use it. Here, that’s the App. Then pass down the state and state setter to all children, and have them use the prop as needed. This is usually a good approach when the state and state setter isn’t used in a whole lot of places.
function App() {
  const [themeName, setThemeName] = useTheme();
  return (
    <div>
      <ThemeSwitcher {...{ themeName, setThemeName }} />
      <Other {...{ themeName, setThemeName }} />
      <div>{themeName}</div>
    </div>
  );
}
  • Or, use the context API instead. It requires more setup, but it’s a good approach when you want to create something in a parent and allow every single descendant to consume it, no matter where it is in the component tree, without manually passing the value(s) as props everywhere.

Leave a Reply