When Should You Use Redux?
Redux has real costs and real benefits - we measured both. The honest criteria for using it, the kinds of apps that do not need it, and how to delete a store that only one screen ever read.
What you will be able to do
- Name the real costs of Redux, with measured numbers
- Name the real benefits, including recording and replaying actions to find a bug
- Apply honest criteria to decide whether a piece of state - or a whole app - needs Redux
- Recognise apps that do not need Redux, including this website
- Remove a Redux slice that only one screen uses, and replace it with useState
- Choose the right tool for each kind of state: useState, lifting, Context, a server cache, or Redux
The idea, in plain English
Lessons 1 to 7 built up the problems Redux solves: drifting copies, unclear ownership, prop drilling, context re-renders. It would be easy to end Module 1 with "so always use Redux". That would be bad advice, and this course will not give it.
Redux is a tool with a cost. It adds code, a library, and concepts every developer on the team must learn. In return it gives one place for shared state, strict rules for changes, a record of every change, and fine-grained updates. Whether that trade is worth it depends on the state you have - not on how big or "serious" the app is.
This lesson measures both sides on small, real examples, then gives a checklist you can apply to your own app. All code was run with React 19.3, react-redux 9.3.0 and Redux Toolkit 2.13.0, in Node 22 and a test browser (jsdom). Bundle sizes were measured with esbuild, minified, in production mode.
Worked example: Deleting a store only one screen ever read: a settings form moved from Redux back to useState - same behaviour, half the code.
1 - The list of products
The products live in the shop’s database, and other people change them. That is server state (Lesson 3). It needs a cache, not a hand-made Redux slice.
Four pieces of state from a shop, each taken through the same questions. Only one of them ends in Redux.
An everyday picture: a truck or a scooter
A delivery truck is the right tool to move a whole house: lots of space, strong, organised. It is the wrong tool to buy a packet of milk: you need a big parking space, it uses more fuel, and it takes longer to start. Nobody says trucks are bad. They are for a certain kind of job.
Redux is the truck. For state that many parts of a large app share and change, it is excellent. For a form on one screen, it is a truck parked outside the corner shop. The skill is not "always truck" or "never truck" - it is knowing which job you have.
What Redux costs - measured
We built the same settings screen twice: once with a Redux slice, once with two useState calls. Both behave the same - we typed "Ravi", unticked the checkbox, and both showed "Saving as Ravi, alerts off".
The Redux version had 42 lines; the useState version had 20. In a browser bundle (minified, production mode, React itself not counted), the Redux version was 27.1 KB - 10.6 KB after gzip - because it brings Redux Toolkit and react-redux. The useState version was 0.4 KB. The library cost is paid once for the whole app, so it matters most for small apps. The code and learning cost is paid every time someone reads or changes the feature: they must follow the action to the reducer to the selector.
CodeA slice, actions, a store, a Provider, selectors. 42 lines vs 20 for our form.Bundle size+10.6 KB gzip for Redux Toolkit + react-redux (paid once per app).ConceptsStore, action, reducer, dispatch, selector, middleware - every team member must learn them.IndirectionA click does not change state directly; it dispatches an action that a reducer handles somewhere else.Example 1: deleting a store only one screen ever read
This is the example from the course outline, and it happens in real codebases more often than you might think. Someone created a settingsPage slice "because the app uses Redux". Only SettingsPage ever reads it. No other screen dispatches its actions.
Use the questions from Lesson 2: who reads it? One component. Who changes it? The same component. Must it survive when the screen closes? No - a fresh form is fine. So it is local state, and the slice can be deleted. The replacement is two useState calls. Same behaviour, half the code, one less thing in the store for everyone else to scroll past.
import { configureStore, createSlice } from "@reduxjs/toolkit";
import { Provider, useDispatch, useSelector } from "react-redux";
// A slice for ONE screen's form
const settingsSlice = createSlice({
name: "settingsPage",
initialState: { displayName: "", emailAlerts: true },
reducers: {
displayNameChanged(state, action) {
state.displayName = action.payload;
},
emailAlertsToggled(state) {
state.emailAlerts = !state.emailAlerts;
},
},
});
const { displayNameChanged, emailAlertsToggled } = settingsSlice.actions;
export const store = configureStore({ reducer: { settingsPage: settingsSlice.reducer } });
function SettingsPage() {
const { displayName, emailAlerts } = useSelector((state) => state.settingsPage);
const dispatch = useDispatch();
return (
<form>
<input value={displayName} onChange={(e) => dispatch(displayNameChanged(e.target.value))} />
<label>
<input type="checkbox" checked={emailAlerts} onChange={() => dispatch(emailAlertsToggled())} />
Email alerts
</label>
<p>Saving as {displayName || "(no name)"}, alerts {emailAlerts ? "on" : "off"}</p>
</form>
);
}
export default function App() {
return (
<Provider store={store}>
<SettingsPage />
</Provider>
);
}import { useState } from "react";
function SettingsPage() {
const [displayName, setDisplayName] = useState("");
const [emailAlerts, setEmailAlerts] = useState(true);
return (
<form>
<input value={displayName} onChange={(e) => setDisplayName(e.target.value)} />
<label>
<input type="checkbox" checked={emailAlerts} onChange={() => setEmailAlerts(!emailAlerts)} />
Email alerts
</label>
<p>Saving as {displayName || "(no name)"}, alerts {emailAlerts ? "on" : "off"}</p>
</form>
);
}
export default function App() {
return <SettingsPage />;
} screen lines bundle (min) gzip
SettingsRedux Saving as Ravi, alerts off 42 27.1 KB 10.6 KB
SettingsLocal Saving as Ravi, alerts off 20 0.4 KB 0.3 KB
(React itself not included in either bundle)Tip: Before deleting a slice, search the code for its actions and its state key. If one screen uses them and nothing else dispatches them, it is local state wearing a Redux costume.
What Redux gives you - and one benefit shown
Earlier lessons already showed most of the benefits: one place for shared state, so copies cannot drift (Lessons 1 and 4); every change is a named action, so you know who changed what (Lesson 4); useSelector, so only the components that read a value re-render (Lessons 2, 6, 7); and RTK Query for server data (Lesson 3).
One benefit has not been shown yet, and it is often the real reason teams choose Redux: because every change is a plain action object, you can record the actions and replay them. Redux DevTools does this in the browser. Below we do it by hand with a tiny middleware.
One source of truthShared state lives in one store; copies cannot drift.Traceable changesEvery change is a named action that can be logged, inspected and replayed.SelectorsComponents re-render only when the value they read changes.MiddlewareOne place for logging, analytics, async logic and error reporting.RTK QueryA built-in cache for server state.Team conventionsEveryone puts shared state and its rules in the same, predictable shape.Example 2: replaying a bug
A customer reports: "my cart says 0 items, but it shows a course". In a codebase where state changes happen anywhere, you would have to guess how they got there. With Redux, the recorder middleware below saves every action of the session.
On a developer’s machine we replayed those actions into a fresh store, one at a time, checking the state after each. The output points to the exact action that broke it: action 3, cart/removed "Docker" - removing an item that was not in the cart still lowered the count. (It is also Lesson 4’s "stored derived value" bug: count should be calculated from items.) This is what Redux DevTools gives you for every action, with no code.
import { configureStore, createSlice } from "@reduxjs/toolkit";
// A cart with a hidden bug: removing an item that is not in the cart still lowers the count
const cartSlice = createSlice({
name: "cart",
initialState: { items: [], count: 0 },
reducers: {
added(state, action) {
state.items.push(action.payload);
state.count++;
},
removed(state, action) {
state.items = state.items.filter((item) => item !== action.payload);
state.count--; // bug: also runs when the item was not there
},
},
});
const { added, removed } = cartSlice.actions;
// A middleware that records every action - like Redux DevTools does
const recorded = [];
const recorder = () => (next) => (action) => {
recorded.push(action);
return next(action);
};
const makeStore = (middleware = []) =>
configureStore({ reducer: { cart: cartSlice.reducer }, middleware: (d) => d().concat(middleware) });
// 1. A user's session in the real app
const store = makeStore([recorder]);
store.dispatch(added("Python"));
store.dispatch(added("Redux"));
store.dispatch(removed("Docker")); // double-click on a stale "remove" button
store.dispatch(removed("Redux"));
console.log("user sees:", store.getState().cart, "<- count does not match items");
// 2. Later, on a developer's machine: replay the recorded actions, one by one
const replay = makeStore();
recorded.forEach((action, i) => {
replay.dispatch(action);
const { items, count } = replay.getState().cart;
const ok = items.length === count ? "ok" : "WRONG";
console.log(" ", i + 1, action.type.padEnd(13), JSON.stringify(action.payload).padEnd(9), "->", JSON.stringify({ items, count }), ok);
});user sees: { items: [ 'Python' ], count: 0 } <- count does not match items
1 cart/added "Python" -> {"items":["Python"],"count":1} ok
2 cart/added "Redux" -> {"items":["Python","Redux"],"count":2} ok
3 cart/removed "Docker" -> {"items":["Python","Redux"],"count":1} WRONG
4 cart/removed "Redux" -> {"items":["Python"],"count":0} WRONGThe honest criteria
Use the left table when you are deciding whether to add Redux. One "yes" is not enough; several together usually are. Then check the second table - if most of your state fits there, you probably do not need Redux at all.
Shared and far apartMany components in different parts of the app read the same client state.Changed from many placesSeveral screens and components update it, not just one owner.Changes oftenFrequent updates where selectors save real re-render work.Complex update rulesAn update affects several values at once, and the rules must be in one place.You need to trace itBugs must be reproduced from user sessions; changes must be logged or audited.A large teamMany developers need one predictable pattern for shared state.Apps that do not need Redux
Many apps never reach those criteria. The most common reason is Lesson 3’s: most of their data is server data, and once a server cache handles it, the client state left over is small and local.
This website is one of them. It has no Redux. Its shared state is small: the logged-in user is in a React Context (an AuthProvider), the theme is saved in the browser, and the "Save" buttons and lesson ticks keep each other up to date with simple browser events. Everything else - courses, progress, saved lessons, comments - is server data. Adding Redux here would add code and 10 KB without solving a problem we have.
Most data comes from a serverUse a server cache (RTK Query, TanStack Query); little client state remains.State is mostly localForms, toggles, tabs - useState, and lifting when needed.Shared values rarely changeTheme, language, user - Context is enough.The app is small, or a content siteBlogs, landing pages, documentation, simple admin screens.Forms are the main workA form library handles fields, validation and errors better than a store.Apps where Redux earns its place
On the other side are apps whose client state is large, shared and busy. A design or document editor, where a selection in one panel changes toolbars, layers and properties panels, and every change must be undoable. A trading or analytics dashboard where many widgets react to the same filters. A chat or collaboration app where real-time updates touch many views at once. In these apps, the costs from the first table are small compared with the bugs Redux prevents.
Common questions
Can I add Redux later? Yes. Start with useState, lifting and Context. When the criteria start to fit, move that specific state into a store. Redux does not have to hold everything - it can start with one slice.
Is Redux outdated? Old Redux code - with long switch statements and many files - had a reputation for boilerplate. Redux Toolkit removed most of it, as the slices in this module show. It is a mature, widely used tool; the question is only whether your state needs it.
What about Zustand, Jotai or MobX? They are other state libraries with different trade-offs. The criteria in this lesson apply to them too: first decide whether you have shared, frequently changing client state at all. This course teaches Redux, but the thinking transfers.
My app already uses Redux for everything. Should I remove it? Not all at once. Use Example 1’s approach slice by slice: find state that only one screen uses, or server data stored by hand, and move it out. Keep Redux for the state that fits the criteria.
This lesson at a glance
Server dataUse a server cache.
RTK Query / TanStack Query
Local stateOne component, or a few close together.
useState, lifting state up
Rarely changing shared valuesTheme, language, user, services.
React Context
Busy shared client stateMany readers and writers, needs tracing.
Redux
Record actionsA middleware sees every action.
() => (next) => (action) => next(action)
Try it yourself
The code does not change. Swap the content string and the program does something else entirely.
“Open a Redux app you know. For each slice, list which components read it. Which slices are used by only one screen?”
“In replay.mjs, fix the removed reducer so it only lowers count when the item was in the cart. Replay again - which lines say WRONG now?”
“Better still: delete count from the state and calculate it from items (Lesson 4). Does the bug still exist?”
“List ten pieces of state in an app you are building. Take each through the four questions in the diagram.”
What usually goes wrong
Size does not decide it - the kind of state does. A big app with mostly server data may need no Redux at all.
If one component reads and changes it, it is local state. Use useState.
✗ createSlice({ name: "settingsPage", ... }) // used only by SettingsPage✓ const [displayName, setDisplayName] = useState("");You must rebuild loading, errors, caching and refetching for every resource. Use a server cache.
✗ productsSlice with loading, error, items and a fetch thunk✓ useGetProductsQuery() // RTK QueryWhen many places read and change the same busy state, hand-built alternatives - one big context, events everywhere - slowly turn into a less tested Redux.
Practice
Write these yourself before opening anything. Getting them wrong first is most of how this sticks.
For each, choose useState, Context, a server cache or Redux: (a) the list of orders, (b) whether a dropdown is open, (c) the language, (d) the selected shapes in a drawing editor, used by the canvas, the toolbar and the layers panel, (e) a sign-up form.
Show hintHide hint
Ask the four questions from the diagram, in order.
Show solutionHide solution
(a) server cache - orders live in the database
(b) useState - one component
(c) Context - shared, rarely changes
(d) Redux - many panels read and change it, often
(e) useState - one screen (or a form library)A store has five slices: auth, cart, settingsPage, productsList, modalOpen. Only the Settings screen uses settingsPage; productsList is fetched from the API; modalOpen is used by one modal. Which slices would you remove, and what replaces each?
Show hintHide hint
Server data, one-screen state, one-component state.
Show solutionHide solution
settingsPage -> useState in the Settings screen
productsList -> RTK Query (server data)
modalOpen -> useState in the modal (or its parent)
Keep in Redux: auth and cart - read and changed across the app.In replay.mjs, which recorded action first made the state wrong, and why?
Show hintHide hint
Look for the first line that says WRONG.
Show solutionHide solution
Action 3: cart/removed "Docker".
"Docker" was not in the cart, so items did not change - but the reducer
still did count--. After that, count no longer matched items.length.Write a state plan for an app
You are building a food delivery app: restaurants and menus, a cart, checkout, live order tracking, a profile page, and dark mode. Before writing any code, decide where each piece of state lives.
- List at least eight pieces of state
- For each, choose useState, lifting, Context, a server cache, or Redux - and give a one-line reason
- Say whether the app needs Redux at all, and for which slices
Show one solutionHide solution
restaurants, menus server cache - owned by the backend
live order status server cache - polled or pushed from the server
profile details server cache - edited on any device
cart (items, notes) Redux - menu pages, header badge and checkout read/change it
selected address Redux - checkout, cart and tracking all use it
dark mode Context - read everywhere, rarely changes
search text on a menu useState - one component
"add note" modal open useState - one component
checkout form fields useState - one screen (or a form library)
Needs Redux? Yes, but small: two slices (cart, selected address).
Everything else is server data, local state or Context.Key points
- Redux has real costs: more code (42 vs 20 lines in our form), about 10.6 KB gzip, and new concepts.
- It has real benefits: one source of truth, traceable and replayable changes, selectors, middleware, RTK Query.
- The kind of state decides, not the size of the app.
- Server data -> a cache. Local state -> useState or lifting. Rarely changing shared values -> Context.
- Redux fits client state that many places read and change, often, and that you need to trace.
- A slice used by only one screen is local state - delete it.
- Many apps, including this website, do not need Redux at all.
Quick check before you move on
Interview questions
When would you choose Redux for a project?
When the app has client state that many distant components read and update frequently, with non-trivial update rules, and when traceability matters - debugging from action logs, auditing, or a large team needing one pattern. Server data goes to a server cache, local state stays in components.
When would you advise against Redux?
For apps whose state is mostly server data or local UI state, small apps and content sites, and form-heavy apps. The extra code, bundle size and indirection would not buy anything there.
What is the most underrated benefit of Redux?
Changes are serialisable actions, so they can be logged, inspected and replayed. Redux DevTools and recorded sessions let you reproduce a bug step by step instead of guessing.
How would you shrink an over-used Redux store?
Audit each slice: move one-screen state back to components, move server data to RTK Query, move rarely changing values to Context, and keep only shared, busy client state in the store - slice by slice, with tests.
Quiz
- 1.
Our settings form: lines of code and gzip size with Redux, and without?
- 2.
How did replaying the recorded actions help find the cart bug?
- 3.
Which of these needs Redux: the theme, a modal’s open state, the cart shared by many pages, the product list?
- 4.
Why does this website not use Redux?
- 5.
Can you add Redux to an app later, one slice at a time?
Comments
Sign in to leave a comment. Your name and photo come from Google; nothing else is shared.
Loading comments...
AI
System Design
Backend
- GraphQL8 modules · 69 lessons planned
- Core Python13 modules · 75 lessons planned
- FastAPI5 sections · 20 lessons
- Node.js14 modules · 206 lessons planned
- Node.js Performance7 chapters · 36 topics
- Event Loop Lifecycle6 phases · 3 scenarios
- Docker & Containerization11 modules · 144 lessons planned
- AWS for Developers14 modules · 219 lessons planned
- CI/CD & DevOps Automation10 modules · 134 lessons planned