The State of State Management

January 22, 2025

State management shouldn’t be a part-time job. Yet every frontend engineer I’ve met in recent years has had to deal with it. While the pace of new state management libraries has slowed, there are still plenty to choose from. After trying almost all of them, I’ve found Zustand to be the best fit for most teams.

I used Redux for years. It works. But in 2025, do you really need actions, reducers, dispatchers, and a boilerplate tax just to update a loading spinner?

Here's why Zustand wins:

  1. No File Gymnastics
// Zustand (single file)
import { create } from 'zustand'

const useUserStore = create<StoreState>((set) => ({
  user: null,
  setUser: (user) => set({ user }),
}))

// Redux Toolkit equivalent (minimum 2 files):
// - store/userSlice.ts (actions/reducers)
// - store/store.ts (root reducer configuration)

Fact: Zustand typically requires ~50% less code than Redux Toolkit for basic to medium-complexity state management. (comparison)

  1. Syntax That Doesn’t Fight TypeScript
// Zustand (inferred types)
const useStore = create((set) => ({
  user: null as User | null,
  setUser: (user: User) => set({ user }),
}))

// Redux Toolkit equivalent (type-safe but verbose)
interface UserState {
  user: User | null
}
const userSlice = createSlice({
  name: 'user',
  initialState: { user: null } as UserState, // Fix: Matches interface
  reducers: {
    setUser: (state, action: PayloadAction<User>) => {
      state.user = action.payload
    },
  },
})

Fact: While both support TypeScript, Zustand’s mutable patterns align better with modern React than Redux’s immutable reducers.

  1. Data Fetching With Simplicity
// Zustand + TanStack Query
import { useQuery } from '@tanstack/react-query'

const useUserData = () => {
  return useQuery({
    queryKey: ['user'],
    queryFn: fetchUser, // No dispatch, no actions
  })
}

// Redux equivalent requires:
// - Thunks/sagas for async logic
// - Reducers to store data
// - Selectors to retrieve it

Fact: It’s simpler to fetch data with Tanstack Query than with Redux Toolkit. Trust me on this.

Why This Matters

  • Fewer abstractions: Zustand + TanStack Query mirrors how React itself works
  • Guardrails where they matter: Redux Toolkit still shines for complex state transitions (e.g., undo/redo)
  • No more "action type" bugs: Zustand’s API surfaces errors at write-time, not runtime

When Redux Still Makes Sense

  • Large teams where strict patterns prevent chaos
  • Apps requiring time-travel debugging via Redux DevTools (one word)
  • Legacy codebases where migration cost > benefit

Bottom line

Zustand + TanStack Query lets us ship features faster by focusing on what the app does instead of how state moves. Less code, fewer bugs, and zero existential debates about where to put a loading spinner

Hot take? Maybe. But I’d rather debug business logic than action type typos.

Subscribe to the Weakly Newsletter

Strong Opinions, Weakly Delivered...

Join me as I dive into:

  • Thoughts on what's shaping the future
  • Lessons from 7+ years of building SaaS
  • Curated gems from across the web

All that, straight to your inbox.

100% free. Unsubscribe anytime.