How to Build a Modern Global Store Using Only React

ruijadom

ruijadom

@ruijadom

Introduction

Many React applications need a global state shared between components without prop drilling or adding heavy dependencies like Redux or Zustand. But is it possible to create a simple, performant, and modern global store using only React, without external libraries or even Context API?

Besides being a practical solution, this is also a great opportunity to understand how popular state management libraries work under the hood, since many of them use React's native APIs.

Why Avoid Context or External Libraries?

  • React Context can cause unnecessary re-renders across the whole component tree consuming it, especially with frequent state changes.
  • Libraries like Redux and Zustand add external dependencies and complexity.
  • We want something simple, lightweight, and using modern React APIs.

Creating Our createStore Function

Let's create a function createStore that generates a global store. It will have:

  • Private internal state
  • Functions to read (get), update (set), and subscribe (subscribe) to state changes
  • A React hook useStore to consume reactive slices of the state

Here's the code:

import { useSyncExternalStore } from 'react'
 
type Listener = () => void
 
export function createStore<T extends object>(initialState: T) {
  let state = initialState
  const listeners = new Set<Listener>()
 
  function get() {
    return state
  }
 
  function set(partial: Partial<T> | ((prev: T) => Partial<T>)) {
    const update = typeof partial === 'function' ? partial(state) : partial
    state = { ...state, ...update }
    listeners.forEach(listener => listener())
  }
 
  function subscribe(listener: Listener) {
    listeners.add(listener)
    return () => listeners.delete(listener)
  }
 
  function useStore<U>(selector: (state: T) => U) {
    return useSyncExternalStore(subscribe, () => selector(state))
  }
 
  return { get, set, subscribe, useStore }
}

How to Use the Store

Create a simple counter store in store/counter.ts:

import { createStore } from '../lib/store'
 
type CounterState = {
  count: number
}
 
export const counterStore = createStore<CounterState>({
  count: 0,
})

Then in your React component consume it like this:

import { counterStore } from './store/counter'
 
export function Counter() {
  const count = counterStore.useStore(state => state.count)
 
  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={() => counterStore.set(s => ({ count: s.count - 1 }))}>–</button>
      <button onClick={() => counterStore.set(s => ({ count: s.count + 1 }))}>+</button>
    </div>
  )
}

Example

See the global store in action in this interactive example:

Advanced Global Store

Open in CodeSandbox ↗
Loading sandbox...

This pattern provides:

  • Global access to state without passing props
  • Components only re-render when the selected slice changes
  • A simple, Zustand-like API

Global State

This store lives outside the React tree:

  • The state is a singleton: there is only one instance shared globally.
  • No need for a Provider wrapper.
  • Cannot create multiple isolated instances of the same store.

If you need isolated scopes, combine with React Context or create store factories.

Besides being simple and efficient, this pattern is a great chance to see how popular global state libraries work under the hood.

For example, Zustand and modern Redux internally use useSyncExternalStore to connect their stores to React efficiently and selectively. By building your own store with this API, you get hands-on understanding of the core techniques used in widely adopted libs.

Advantages and Caveats

Advantages

  • Zero external dependencies
  • Simple and intuitive API
  • Avoids full tree re-renders caused by Context
  • Built on official, stable React API

Caveats

  • Global singleton state can complicate multi-tenant apps or isolated testing
  • Persistence and middleware must be implemented manually
  • State immutability is important to avoid bugs

Next Steps

You can extend this pattern to:

  • Create multiple stores (userStore, settingsStore)
  • Add persistence with localStorage
  • Implement middlewares for logging or DevTools
  • Create advanced selectors to optimize re-renders

Conclusion

With just a few lines and the right React API, you can create a modern, lightweight, performant global store without external dependencies. This pattern fits many React apps perfectly.

Try it in your next project and enjoy simple, effective global state management!