Back to Blog
Zustand vs Redux Toolkit: State Management in 2024

Zustand vs Redux Toolkit: State Management in 2024

Ansh Gupta
5 min read
ReactState ManagementZustandRedux ToolkitJavaScriptPerformance

Zustand vs Redux Toolkit: State Management in 2024

After building apps with both, here's my unbiased comparison of Zustand and Redux Toolkit (RTK).

Quick Comparison

FeatureZustandRedux Toolkit
Bundle Size8KB36KB
Learning Curve5 minutes2 hours
TypeScriptGreatExcellent
DevToolsGoodExcellent
EcosystemGrowingMassive
PerformanceExcellentExcellent

Code Comparison

Simple Counter

Zustand

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

interface CounterStore {
  count: number
  increment: () => void
  decrement: () => void
}

export const useCounter = create<CounterStore>((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
}))

// Component.tsx
function Counter() {
  const { count, increment, decrement } = useCounter()
  return (
    <div>
      <button onClick={decrement}>-</button>
      <span>{count}</span>
      <button onClick={increment}>+</button>
    </div>
  )
}

Redux Toolkit

// store.ts
import { configureStore, createSlice } from '@reduxjs/toolkit'

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

export const { increment, decrement } = counterSlice.actions
export const store = configureStore({
  reducer: { counter: counterSlice.reducer },
})

// Component.tsx  
import { useSelector, useDispatch } from 'react-redux'

function Counter() {
  const count = useSelector((state) => state.counter.value)
  const dispatch = useDispatch()
  
  return (
    <div>
      <button onClick={() => dispatch(decrement())}>-</button>
      <span>{count}</span>
      <button onClick={() => dispatch(increment())}>+</button>
    </div>
  )
}

Advanced Patterns

Async Actions

Zustand

const useStore = create((set) => ({
  users: [],
  loading: false,
  fetchUsers: async () => {
    set({ loading: true })
    const users = await api.getUsers()
    set({ users, loading: false })
  },
}))

Redux Toolkit

const usersSlice = createSlice({
  name: 'users',
  initialState: { list: [], loading: false },
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(fetchUsers.pending, (state) => {
        state.loading = true
      })
      .addCase(fetchUsers.fulfilled, (state, action) => {
        state.list = action.payload
        state.loading = false
      })
  },
})

export const fetchUsers = createAsyncThunk(
  'users/fetch',
  async () => api.getUsers()
)

Performance Deep Dive

Re-render Optimization

Zustand

// Automatic selector optimization
const count = useStore((state) => state.count) // Only re-renders on count change

Redux Toolkit

// Requires createSelector for similar optimization
const selectCount = createSelector(
  [(state) => state.counter],
  (counter) => counter.value
)

Real-World Scenarios

When to Use Zustand

  1. Small to medium apps - Less boilerplate
  2. Rapid prototyping - Get running in minutes
  3. Component-level state - Replace Context API
  4. Simple async - Built-in async support
// Perfect Zustand use case: Shopping cart
const useCart = create((set) => ({
  items: [],
  addItem: (item) => set((state) => ({ 
    items: [...state.items, item] 
  })),
  removeItem: (id) => set((state) => ({
    items: state.items.filter(item => item.id !== id)
  })),
  total: 0,
  calculateTotal: () => set((state) => ({
    total: state.items.reduce((sum, item) => sum + item.price, 0)
  })),
}))

When to Use Redux Toolkit

  1. Large applications - Better structure
  2. Complex state logic - Time travel, middleware
  3. Team projects - Established patterns
  4. Heavy async - RTK Query is amazing
// Perfect RTK use case: Complex data fetching
export const apiSlice = createApi({
  reducerPath: 'api',
  baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
  tagTypes: ['User', 'Post'],
  endpoints: (builder) => ({
    getUsers: builder.query({
      query: () => 'users',
      providesTags: ['User'],
    }),
    updateUser: builder.mutation({
      query: ({ id, ...patch }) => ({
        url: `users/${id}`,
        method: 'PATCH',
        body: patch,
      }),
      invalidatesTags: ['User'],
    }),
  }),
})

Migration Strategies

From Redux to Zustand

// Gradual migration - use both temporarily
const useHybridStore = () => {
  const reduxData = useSelector(state => state.oldFeature)
  const zustandData = useNewStore()
  return { ...reduxData, ...zustandData }
}

From Zustand to Redux

// Convert Zustand stores to RTK slices
const slice = createSlice({
  name: 'feature',
  initialState: zustandStore.getState(),
  reducers: {
    // Map Zustand actions to reducers
  }
})

My Recommendations

Choose Zustand When:

  • Building SPAs under 50 components
  • Need minimal setup
  • Working solo or small team
  • Performance is critical
  • Learning React

Choose Redux Toolkit When:

  • Building enterprise applications
  • Need extensive middleware
  • Working with large teams
  • Complex debugging required
  • Using RTK Query

Performance Benchmarks

OperationZustandRTK
Store creation0.1ms0.8ms
Simple update0.05ms0.08ms
Selector (1000 items)0.2ms0.3ms
DevTools overhead~5%~8%

Final Verdict

There's no clear winner. Both are excellent in 2024.

  • Zustand: Perfect for 80% of apps. Simple, fast, delightful.
  • Redux Toolkit: Best for complex apps needing structure and tooling.

My approach: Start with Zustand. If you need Redux features, you'll know. The migration is straightforward if needed.

Both will serve you well. Pick based on your needs, not hype.

AG

About Ansh Gupta

Frontend Developer with 3 years of experience building modern web applications. Based in Indore, India, passionate about React, TypeScript, and creating exceptional user experiences.

Learn more about me