External State Stores

For large apps, libraries like Zustand or Redux manage global state.

As apps grow, some teams move shared state into a dedicated store library. These handle updates efficiently and often avoid re-rendering components that do not use the changed slice of state.

Popular choices

  • Zustand — a tiny hook-based store with minimal boilerplate.
  • Redux Toolkit — the modern, opinionated Redux for larger apps.
  • Jotai / Recoil — atom-based state.

React also provides useSyncExternalStore, the official hook libraries use to subscribe components to an external store safely.

Example

Example · javascript
// Example using Zustand (npm install zustand)
import { create } from 'zustand';

const useStore = create((set) => ({
  count: 0,
  increment: () => set((s) => ({ count: s.count + 1 })),
}));

function Counter() {
  const { count, increment } = useStore();
  return <button onClick={increment}>Count: {count}</button>;
}

When to use it

  • A large e-commerce site migrates from prop drilling to Zustand so the cart state is accessible in the header, product page, and checkout flow without a single context provider.
  • A Redux-powered admin panel uses Redux Toolkit slices to manage normalised server data so multiple views can read the same entities without duplicate fetch calls.
  • A game uses Zustand's subscribe API to connect React components to a non-React game loop, keeping UI in sync with engine state without wrapping the loop in React.

More examples

Zustand store setup

Creates a Zustand store with items state and three actions — no boilerplate providers or reducers needed.

Example · js
import { create } from 'zustand';

export const useCartStore = create(set => ({
  items: [],
  addItem: item => set(state => ({ items: [...state.items, item] })),
  removeItem: id => set(state => ({
    items: state.items.filter(i => i.id !== id),
  })),
  clear: () => set({ items: [] }),
}));

Read Zustand store in component

Each component subscribes to only the slice of store state it needs, avoiding unnecessary re-renders.

Example · jsx
import { useCartStore } from './cartStore';

function CartBadge() {
  const count = useCartStore(state => state.items.length);
  return <span className="badge">{count}</span>;
}

function AddToCartButton({ product }) {
  const addItem = useCartStore(state => state.addItem);
  return (
    <button onClick={() => addItem(product)}>Add to cart</button>
  );
}

Redux Toolkit slice

Defines a Redux Toolkit slice with three reducers; RTK's Immer integration allows direct mutation syntax safely.

Example · js
import { createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    increment: state => { state.value += 1; },
    decrement: state => { state.value -= 1; },
    reset:     state => { state.value = 0; },
  },
});

export const { increment, decrement, reset } = counterSlice.actions;
export default counterSlice.reducer;

Discussion

  • Be the first to comment on this lesson.