modern patterns · beyond redux

State Management Patterns
Beyond Redux

Exploring Zustand, Jotai, Recoil, Valtio, MobX, XState & more

❝ Redux revolutionized frontend state management with its predictable, unidirectional data flow. But the ecosystem has evolved. Today we have a spectrum of tools that offer simpler APIs, better TypeScript integration, and optimized performance—all while preserving the benefits of a single source of truth or introducing reactive primitives.❞

This article explores the most compelling state management patterns beyond Redux. We'll examine atomic state (Jotai, Recoil), immutable stores (Zustand), proxy-based reactivity (Valtio, MobX), and even state machines (XState). By the end, you'll understand which pattern fits your project's complexity, team size, and scaling needs.

Why look beyond Redux?

Redux is still a solid choice, but it often introduces boilerplate and conceptual overhead. Modern alternatives aim to reduce that friction while maintaining excellent developer experience:

Developer survey (2024): 42% of React developers now use Zustand as their primary state manager, while 28% use Redux. The shift reflects the desire for simplicity without sacrificing power.

1. Zustand: Minimalist, scalable stores

Zustand (German for "state") is a small, fast, and scalable state management library. It provides a hook-based API with no providers, making it incredibly easy to integrate.

// store.js
import { create } from 'zustand'

const useStore = create((set) => ({
  bears: 0,
  increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
  removeAllBears: () => set({ bears: 0 }),
}))

// Component usage
function BearCounter() {
  const bears = useStore((state) => state.bears)
  const increase = useStore((state) => state.increasePopulation)
  return <button onClick={increase}>🐻 {bears}</button>
}

Why it shines: Zustand supports middleware (persist, redux devtools), slices for large stores, and works outside React. Its selector pattern prevents unnecessary re-renders. Perfect for mid-to-large applications where Redux felt heavy.

2. Jotai: Atomic state for React

Jotai (Japanese for "state") takes inspiration from Recoil but with a simpler API. State is defined as atoms, which can be composed and derived. It enables granular updates without extra re-renders.

import { atom, useAtom } from 'jotai'

// primitive atom
const countAtom = atom(0)
// derived atom
const doubleCountAtom = atom((get) => get(countAtom) * 2)

function Counter() {
  const [count, setCount] = useAtom(countAtom)
  const [double] = useAtom(doubleCountAtom)
  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>+</button>
      <p>Count: {count}, Double: {double}</p>
    </div>
  )
}

Key strengths: Built-in async support, TypeScript inference, and the ability to split atoms across bundles. Jotai eliminates the need for selectors and providers (except optional Provider for scope). Great for applications with complex derived state.

3. Valtio: Proxy-powered simplicity

Valtio makes state mutable but tracks changes via proxies. You write code as if mutating a plain object, but only the components that depend on changed properties re-render.

import { proxy, useSnapshot } from 'valtio'

const state = proxy({ count: 0, text: 'hello' })

function Counter() {
  const snap = useSnapshot(state)
  return (
    <div>
      <button onClick={() => state.count++}>+</button>
      <p>Count: {snap.count}</p>
    </div>
  )
}

Why choose Valtio: Minimal mental model—no reducers, no selectors. Works great for forms, local state that needs to be shared, and for quick prototypes. Combines well with Zustand for specific use cases.

4. Recoil: Facebook's graph-based state

Recoil introduces atoms and selectors, forming a directed graph. It's built for React and provides concurrent mode compatibility, fine-grained updates, and built-in persistence.

import { atom, selector, useRecoilState, useRecoilValue } from 'recoil'

const textState = atom({ key: 'textState', default: '' })
const charCountState = selector({
  key: 'charCountState',
  get: ({ get }) => get(textState).length,
})

function CharacterCounter() {
  const [text, setText] = useRecoilState(textState)
  const count = useRecoilValue(charCountState)
  return (
    <div>
      <input value={text} onChange={(e) => setText(e.target.value)} />
      <p>Character Count: {count}</p>
    </div>
  )
}

When to use: Recoil shines in complex data-driven apps where you need derived state and async queries. However, its adoption has slowed compared to Zustand and Jotai due to a slightly steeper learning curve.

5. MobX: Transparent functional reactive programming

MobX uses observable state and reactions. It's been around for years, but remains a solid choice for applications that benefit from automatic dependency tracking.

import { makeAutoObservable } from 'mobx'
import { observer } from 'mobx-react-lite'

class TimerStore {
  seconds = 0
  constructor() {
    makeAutoObservable(this)
  }
  increment() {
    this.seconds += 1
  }
  reset() {
    this.seconds = 0
  }
}

const timer = new TimerStore()

const TimerView = observer(() => (
  <button onClick={() => timer.increment()}>
    Seconds: {timer.seconds}
  </button>
))

Pros & cons: MobX reduces boilerplate dramatically, but the "magic" can be confusing for newcomers. It's excellent for domain models and applications with many derived values.

6. XState: State machines & actors

For complex UI flows (wizards, authentication, multi-step forms), XState provides a formal model of states and transitions. It works with any framework and even visualizes statecharts.

import { createMachine, interpret } from 'xstate'

const toggleMachine = createMachine({
  id: 'toggle',
  initial: 'inactive',
  states: {
    inactive: { on: { TOGGLE: 'active' } },
    active: { on: { TOGGLE: 'inactive' } }
  }
})

const toggleService = interpret(toggleMachine).start()

// React integration with @xstate/react
import { useMachine } from '@xstate/react'
function Toggle() {
  const [state, send] = useMachine(toggleMachine)
  return (
    <button onClick={() => send('TOGGLE')}>
      {state.value === 'inactive' ? 'Off' : 'On'}
    </button>
  )
}

When to adopt: XState is ideal for mission-critical logic where you need predictability, testing, and visual documentation. It can also manage side effects with actors.

Comparison at a glance

LibraryParadigmLearning curveBundle sizeBest for
ZustandStore + hooksLow~1kBGeneral-purpose, scalable apps
JotaiAtomicLow~3kBComposable derived state
ValtioProxy mutableVery low~4kBForms, local-first apps
RecoilAtom/selector graphMedium~15kBComplex async dependencies
MobXObservableMedium~16kBDomain-heavy apps, OOP style
XStateState machineHigh~12kBComplex flows, robust logic
Tip: You can mix patterns! Use Zustand for global app state, Jotai for local component state that needs sharing, and XState for a checkout wizard. Each tool solves a specific problem.

How to choose the right pattern for your team

Consider these factors:

A good starting point for most React projects today is Zustand + Jotai — Zustand for global stores, Jotai for component-specific reactive atoms. They integrate seamlessly and keep your bundle light.

Real-world adoption: from Redux to Zustand

A fintech dashboard with 50+ screens migrated from Redux to Zustand. The results after 6 months:

The team still uses Redux Toolkit for specific modules requiring complex middleware, but the core app now leverages Zustand's simplicity without sacrificing scalability.

Future trends: signals, server components, and beyond

The frontend landscape is shifting. React Server Components (RSC) change where state lives. Libraries like Zustand and Jotai are adapting to work with RSC. Meanwhile, new frameworks like Preact Signals are inspiring reactive primitives. Expect more convergence between local state management and server state tools (TanStack Query, RTK Query).

The key takeaway: state management is no longer one-size-fits-all. The best solution is often a combination of tools tailored to each layer of your application.

Final thoughts: embrace the ecosystem

Redux isn't dead—it's still a robust choice for teams who love its predictability. But the rise of Zustand, Jotai, Valtio, and others gives us the freedom to pick the right abstraction for the problem at hand. The best state management strategy is the one that makes your team productive and your code maintainable.

Experiment with these libraries in a side project. Feel the difference in developer experience. You'll likely find that simpler, more targeted state solutions lead to cleaner code and faster delivery.

Happy coding — and may your state always be predictable.

Zustand Jotai Valtio Recoil MobX XState React State Frontend Architecture Beyond Redux Modern JS