qttn.dev

We have built-in global state in React

Jul 6, 2025

Redux, Zustand, Jotai, XState, and many other state management libraries are great, but did you know that React has a built-in way to manage global state? Well, not exactly, but you can use useState in a clever way to achieve that.

Why? It’s not good performance-wise, but it works and we love pushing the boundaries of what we can do with React.

Let’s start with a simple example

Suppose we have two simple React components:

let count = 0;
let setCount: Dispatch<React.SetStateAction<number>> = () => {
  throw new Error("setCount is not defined");
};

export function App() {
  [count, setCount] = useState(0);

  return (
    <div className="flex flex-col items-center justify-center gap-4">
      <p>You clicked {count} times</p>
      <button className="btn" onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

export function App2() {
  return (
    <div className="mt-4 flex flex-col items-center justify-center gap-4">
      <p>You clicked {count} times</p>
      <button
        className="btn"
        onClick={() => {
          setCount(count + 1);
        }}
      >
        Click me from App 2
      </button>
    </div>
  );
}

That seems odd…

Does that even work? Well, try it yourself.

Well, the setting part works, but the getting part of the bottom one is not properly synced. That’s because React’s useState must be called in a component in order to subscribe to the state changes.

So how do we fix that?

We can do this funny little thing that set another random state to force a re-render:

export function App2() {
  const [_, setRerender] = useState(false);

  function rerender() {
    setRerender((prev) => !prev);
  }

  return (
    <div className="mt-4 flex flex-col items-center justify-center gap-4">
      <p>You clicked {count} times</p>
      <button
        className="btn"
        onClick={() => {
          setCount(count + 1);
          rerender();
        }}
      >
        Click me from App 2
      </button>
    </div>
  );
}

But we have a little problem here: the second component (App2) use App1’s setter, so it notifies the first component to re-render, but App1 setter by itself does not notify App2 to re-render. So you can see that button in App1 does not update the count in App2 when you click it.

Let’s make every setter notify every setter!

We can create an IIFE to encapsulate the state:

const useCounter = (() => {
  let state = 0;
  const setters = new Set<Dispatch<React.SetStateAction<number>>>();

  function setState(newState: React.SetStateAction<number>) {
    if (typeof newState === "function") {
      state = (newState as Function)(state);
    } else {
      state = newState;
    }
    setters.forEach((setter) => setter(state));
  }

  return () => {
    const [localState, setLocalState] = useState(state);

    setters.add(setLocalState);

    return [localState, setState] as const;
  };
})();

Now we can use this useCounter function inside our components like normal. You can see that it properly syncs the state across all components that use it, because it calls all the setters whenever the state changes.

And voila! We have a global state that is properly synced across all components.

Or just use Zustand, idk.