Redux provides predictable state with a single store, actions, and reducers (RTK simplifies this with createSlice). Zustand offers a lighter alternative. Choose based on app complexity — most apps can start with useState + Context.
Single store, actions describe events, reducers produce new state, dispatch triggers the flow. Predictable and debuggable.
createSlice generates actions + reducer together with Immer for ergonomic immutable updates. configureStore sets up sensible defaults.
useSelector subscribes to store slices (re-renders only when selected value changes). useDispatch returns the dispatch function.
Minimal API, no boilerplate, no Providers — increasingly popular for apps that don't need Redux's full complexity.
Start with useState + Context. Add Zustand for moderate complexity. Use Redux Toolkit for complex enterprise apps. TanStack Query for server state.
As React applications grow, managing state across many components becomes complex. External state management libraries provide centralized stores, predictable update patterns, and tools for debugging.
Redux implements a unidirectional data flow with four building blocks:
{ type: 'todos/add', payload: 'Buy milk' }dispatch({ type: 'todos/add', payload: 'Buy milk' })The flow: user interaction → dispatch action → reducer produces new state → components re-render.
RTK is the official, modern way to write Redux. It eliminates boilerplate:
createSlice({ name, initialState, reducers }) — generates action creators and reducer in one declaration. Uses Immer internally so you can write "mutating" logic that produces immutable updates.configureStore({ reducer }) — sets up the store with good defaults (DevTools, thunk middleware)createAsyncThunk('name', async fn) — handles async logic (API calls) with pending/fulfilled/rejected lifecycle actionsuseSelector(selector) — subscribes to the store and returns selected state. The component only re-renders when the selected value changes (strict equality === by default). Selectors should be pure functions: const todos = useSelector(state => state.todos).useDispatch() — returns the store's dispatch function for sending actions.These hooks replaced the older connect() HOC, which wrapped components and mapped state/dispatch to props.
Redux adds complexity — don't use it by default. Consider Redux when:
For simple apps, useState + useReducer + Context is sufficient.
const useStore = create(set => ({ count: 0, increment: () => set(state => ({ count: state.count + 1 })) })). Increasingly popular for its simplicity.useState + ContextRedux provides a predictable, debuggable state container with actions + reducers + single store. RTK eliminates Redux boilerplate. useSelector subscribes to specific state slices, useDispatch sends actions. Zustand is the leading lightweight alternative. Most apps should start simple (useState + Context) and add a library only when complexity demands it.
Fun Fact
Redux was created by Dan Abramov and Andrew Clark in 2015, inspired by Elm and Flux. Dan created it as a demo for his React Europe talk on time-travel debugging. He later joined the React team at Facebook, and ironically spent years telling people most apps don't need Redux.