A Minimal Implementation of Global Store in React

A minimal global state management library inspired by Zustand, built to understand how state management works under the hood.

TypeScriptReact

I wanted to understand how libraries like Zustand actually work. Not just the API, but the internals. So I built one from scratch.

The core: a pub-sub store

The entire state management system is built on a simple pub-sub pattern. A createStore function holds state in a closure, maintains a Set of listeners, and notifies them on every setState call.

function createStore<T>(createState: StateCreator<T>): StoreApi<T> {
  let state: T;
  const listeners = new Set<Listener>();
 
  const setState = (partial) => {
    const nextState =
      typeof partial === "function" ? partial(state) : partial;
 
    state = Object.assign({}, state, nextState);
    listeners.forEach((listener) => listener());
  };
 
  const getState = () => state;
 
  const subscribe = (listener: Listener) => {
    listeners.add(listener);
    return () => listeners.delete(listener);
  };
 
  const api = { setState, getState, subscribe, destroy };
  state = createState(setState, getState, api);
  return api;
}

That's it. No framework magic. Just a closure, a Set, and Object.assign.

Connecting to React

The tricky part is making React re-render only when the selected state changes. The hook subscribes to the store on mount, runs a selector on every state change, and only calls setState if the selected value actually changed.

const useStore = <U>(
  selector = (state) => state,
  equalityFn = Object.is
) => {
  const [selectedState, setSelectedState] = useState(() =>
    selector(api.getState())
  );
  const selectorRef = useRef(selector);
  const equalityFnRef = useRef(equalityFn);
  const selectedStateRef = useRef(selectedState);
 
  useEffect(() => {
    const listener = () => {
      const next = selectorRef.current(api.getState());
      if (!equalityFnRef.current(selectedStateRef.current, next)) {
        selectedStateRef.current = next;
        setSelectedState(next);
      }
    };
    return api.subscribe(listener);
  }, []);
 
  return selectedState;
};

The refs are critical here. Without them, the effect would need the selector and equality function as dependencies, causing resubscription on every render.

Selector optimization

This is where it gets interesting. Each component can subscribe to a different slice of state:

// Only re-renders when count changes
const count = useStore((state) => state.count);
 
// Only re-renders when the filtered list changes
const activeTodos = useStore((state) =>
  state.todos.filter((t) => !t.completed)
);

For object selections, a shallow comparison prevents unnecessary re-renders:

function shallow<T>(objA: T, objB: T): boolean {
  if (Object.is(objA, objB)) return true;
  if (typeof objA !== "object" || typeof objB !== "object") return false;
 
  const keysA = Object.keys(objA);
  const keysB = Object.keys(objB);
  if (keysA.length !== keysB.length) return false;
 
  for (const key of keysA) {
    if (!Object.is(objA[key], objB[key])) return false;
  }
  return true;
}

The API it produces

The create function returns a hook with store methods attached directly to it:

const useCounterStore = create<CounterState>(() => ({
  count: 0,
}));
 
// In a component
const count = useCounterStore((state) => state.count);
 
// Outside a component
useCounterStore.setState({ count: 5 });
useCounterStore.getState().count;

This pattern of attaching setState, getState, and subscribe directly to the hook is exactly what Zustand does. It means you can use the store anywhere, not just inside React components.

What I took away from this

  • useSyncExternalStore exists for a reason. My implementation uses useState + useEffect, which can cause tearing in concurrent mode. Zustand v4+ uses useSyncExternalStore to avoid this. That was the biggest gap I found.
  • Selectors are the performance story. Without selectors, every component subscribing to the store re-renders on every state change. The selector + equality check pattern is what makes fine-grained subscriptions work.
  • Refs solve the stale closure problem. The listener runs in a useEffect closure, but needs access to the latest selector and equality function. Refs bridge that gap.
  • The pub-sub pattern is everywhere. Once you see it here, you start recognizing it in event emitters, Redux, MobX, and even the DOM event system.

The whole library is ~120 lines of TypeScript. For production, use Zustand. But building this gave me a mental model I couldn't have gotten from reading docs.