What is Redux? Create a Redux slice
Redux is a predictable global state container based on a single store, read-only state, and pure reducers updating state via dispatched actions. Modern Redux uses Redux Toolkit: createSlice generates the reducer + action creators with Immer-powered 'mutating' syntax.
Redux is a predictable state-management library — a single global store, state that's only changed by dispatching actions, and pure reducers that compute the next state. Its three principles:
- Single source of truth — one store holds app state.
- State is read-only — you change it only by dispatching an action (a
{ type, payload }object describing what happened). - Changes via pure reducers —
(state, action) => newState, no side effects, no mutation.
The flow: dispatch(action) → reducer → new state → subscribed components re-render. The benefits are predictability, time-travel debugging, and a clear audit trail of every change.
Modern Redux = Redux Toolkit (RTK)
Classic Redux had a lot of boilerplate (action types, action creators, switch-statement reducers, immutable spread everywhere). Redux Toolkit is the official, recommended way — createSlice collapses all of it.
Creating a slice
import { createSlice } from "@reduxjs/toolkit";
const counterSlice = createSlice({
name: "counter",
initialState: { value: 0 },
reducers: {
increment: (state) => {
state.value += 1; // "mutating" — Immer makes it immutable under the hood
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
},
},
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;createSlice auto-generates the action creators and action types from the reducers keys. The "mutating" code is safe because RTK uses Immer — you write mutations, Immer produces an immutable update.
Wiring it up
// store.js
const store = configureStore({ reducer: { counter: counterReducer } });
// component
const value = useSelector((s) => s.counter.value);
const dispatch = useDispatch();
dispatch(increment());For async, RTK has createAsyncThunk (or RTK Query for server data).
When (and when not) to use Redux
- Good fit: large app, complex global client state shared widely, need for devtools/middleware/time-travel, a big team wanting a known pattern.
- Often unnecessary: server state belongs in React Query/RTK Query, not hand-managed in Redux; small apps do fine with
useState/Context/Zustand. Don't reach for Redux reflexively.
How to answer
"Redux is a predictable state container — single store, read-only state, pure reducers, changes via dispatched actions. In modern code I use Redux Toolkit: createSlice generates the reducer and action creators, and Immer lets me write 'mutating' updates safely. But I'd scope it — server state goes to RTK Query/React Query, and small apps don't need Redux at all."
Follow-up questions
- •How does Immer let you write 'mutating' reducers safely?
- •What does createSlice generate for you?
- •Why use RTK Query / React Query instead of Redux for server data?
- •When is Redux overkill?
Common mistakes
- •Using classic Redux boilerplate instead of Redux Toolkit.
- •Storing server data in Redux and hand-rolling caching/loading flags.
- •Mutating state in a plain reducer outside Immer's context.
- •Reaching for Redux on a small app that doesn't need it.
Performance considerations
- •useSelector re-renders a component when its selected slice changes — keep selectors narrow and memoized. configureStore includes good defaults. For server data, RTK Query dedupes and caches; rolling your own in reducers is slower to build and error-prone.
Edge cases
- •Async flows — createAsyncThunk or RTK Query.
- •Normalizing relational state (createEntityAdapter).
- •SSR store hydration.
- •Selector performance — memoized selectors (reselect).
Real-world examples
- •A counter or cart slice with createSlice; createAsyncThunk for an API call.
- •Large multi-team apps using RTK for structure + devtools, RTK Query for server state.