Introduction

Managing global state in React can become challenging as your application grows. While React’s built-in state (useState, useReducer, and context) works well for simple cases, it often becomes difficult to handle complex and deeply shared state across multiple components.
To solve this, lightweight state management libraries like Zustand and Jotai offer simple, scalable, and high-performance ways to manage global state with minimal code.

In this article, you’ll learn how Zustand and Jotai work, how to use them in real-world React applications, and how to decide which one fits your needs.

Why Not Just Use React Context?

React Context is useful but has limitations:

Zustand and Jotai solve these issues with:

What Is Zustand?

Zustand is a small, fast, and scalable state management library.

Key Features

Install Zustand

npm install zustand

Creating a Store in Zustand

Example Store

import { create } from 'zustand';

const useUserStore = create((set) => ({
  user: null,
  setUser: (data) => set({ user: data }),
  clearUser: () => set({ user: null })
}));

Using the Store in a Component

function Profile() {
  const user = useUserStore((state) => state.user);
  const setUser = useUserStore((state) => state.setUser);

  return (
    <div>
      <p>User: {user?.name ?? 'No user'}</p>
      <button onClick={() => setUser({ name: 'Alex' })}>Login</button>
    </div>
  );
}

Why Zustand Works Well

Handling Complex or Nested State with Zustand

Zustand makes complex state easy.

Example

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

Advantages

Async State Logic in Zustand

Example Fetching Data

const useProductStore = create((set) => ({
  products: [],
  fetchProducts: async () => {
    const res = await fetch('/api/products');
    const data = await res.json();
    set({ products: data });
  }
}));

Why It’s Powerful

What Is Jotai?

Jotai is a minimalistic state management library based on atoms.

Key Features

Install Jotai

npm install jotai

Creating Atoms in Jotai

Example Atom

import { atom, useAtom } from 'jotai';

const countAtom = atom(0);

Using an Atom

function Counter() {
  const [count, setCount] = useAtom(countAtom);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount((c) => c + 1)}>Increment</button>
    </div>
  );
}

Why Jotai Works Well

Derived Atoms (Computed State in Jotai)

Jotai allows derived state based on other atoms.

Example

const priceAtom = atom(100);
const taxAtom = atom(10);

const totalAtom = atom((get) => get(priceAtom) + get(taxAtom));

Benefits

Async Atoms in Jotai

Jotai supports async atoms easily.

Example

const userAtom = atom(async () => {
  const res = await fetch('/api/user');
  return await res.json();
});

Why It’s Useful

Zustand vs Jotai — Which Should You Use?

FeatureZustandJotai
StyleSingle global storeMany small atoms
Best forComplex and large appsUI state, small to medium apps
BoilerplateMinimalVery minimal
Async logicBuilt-inBuilt-in
PerformanceExcellentExcellent
Learning curveVery easyVery easy

Simple Recommendation

Best Practices for Managing Global State

Advanced Patterns in Zustand

Zustand supports powerful patterns that help manage complex application logic at scale.

1. Middleware: Persisting State to LocalStorage

import { create } from 'zustand';
import { persist } from 'zustand/middleware';

const useThemeStore = create(
  persist(
    (set) => ({
      theme: 'light',
      toggleTheme: () => set((state) => ({ theme: state.theme === 'light' ? 'dark' : 'light' }))
    }),
    {
      name: 'theme-storage'
    }
  )
);

2. Zustand + DevTools Integration

import { devtools } from 'zustand/middleware';

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

3. Zustand Slices for Large Apps

const createUserSlice = (set) => ({
  user: null,
  login: (u) => set({ user: u })
});

const createCartSlice = (set) => ({
  cart: [],
  addToCart: (item) => set((s) => ({ cart: [...s.cart, item] }))
});

const useStore = create((set) => ({
  ...createUserSlice(set),
  ...createCartSlice(set)
}));

Advanced Patterns in Jotai

1. Atom Families (Dynamic Atoms)

Used for dynamic lists like form fields.

import { atomFamily } from 'jotai/utils';

const fieldAtom = atomFamily((id) => atom(`Field-${id}`));

2. Writable Derived Atoms

const firstNameAtom = atom('John');
const lastNameAtom = atom('Doe');

const fullNameAtom = atom(
  (get) => `${get(firstNameAtom)} ${get(lastNameAtom)}`,
  (get, set, value) => {
    const [first, last] = value.split(' ');
    set(firstNameAtom, first);
    set(lastNameAtom, last);
  }
);

3. Splitting Global State Into Logical Atoms

Avoid large objects; break state into atomic units.

Real-World Examples

1. Zustand Cart System

const useCartStore = create((set) => ({
  cart: [],
  add: (item) => set((s) => ({ cart: [...s.cart, item] })),
  remove: (id) => set((s) => ({ cart: s.cart.filter((i) => i.id !== id) })),
  total: () => useCartStore.getState().cart.reduce((t, i) => t + i.price, 0)
}));

2. Jotai Theme Toggle

const themeAtom = atom('light');
const toggleThemeAtom = atom(
  (get) => get(themeAtom),
  (get, set) => set(themeAtom, get(themeAtom) === 'light' ? 'dark' : 'light')
);

3. Jotai Filter State for a Product List

const searchAtom = atom('');
const categoryAtom = atom('all');
const productsAtom = atom(async () => await fetch('/api/products').then(r => r.json()));

const filteredProductsAtom = atom((get) => {
  const search = get(searchAtom).toLowerCase();
  const category = get(categoryAtom);
  return get(productsAtom).filter((p) =>
    p.name.toLowerCase().includes(search) &&
    (category === 'all' || p.category === category)
  );
});

Comparison Chart: Zustand vs Jotai vs Redux Toolkit vs Recoil

FeatureZustandJotaiRedux ToolkitRecoil
BoilerplateVery LowVery LowHighMedium
Learning CurveEasyEasyMediumMedium
Best Use CaseLarge apps, complex logicUI state, atomic stateEnterprise-scale architectureApp-wide state with relationships
PerformanceExcellentExcellentGoodVery Good
Async SupportBuilt-inBuilt-inNeeds Thunks/SagasBuilt-in
DevToolsYesYesYesYes
State ModelSingle StoreAtomsReducers/ActionsAtoms/Selectors

Summary

Conclusion

Managing global state in React becomes much easier with Zustand and Jotai. Both libraries are lightweight, fast, and developer-friendly, offering clean APIs for sharing and updating state across components. Zustand is ideal for larger, more complex apps, while Jotai is perfect for atomic UI state and modular architecture. With the right choice and good practices, you can maintain clean, scalable, and high-performance React applications.