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.