← Back to Redux
Lesson 8 · State Management Fundamentals

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.

Beginner30 min

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.

workflowWhich tool for this state?step 1 / 4

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.

state
products
owner
the server
tool
RTK Query
Redux slice?
no

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.

The costs
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.

Before - SettingsRedux.jsx
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> ); }
After - SettingsLocal.jsx
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 />; }
Typed "Ravi", unticked alerts - measured
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.

The benefits
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.

replay.mjs
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); });
Output - node replay.mjs
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} WRONG

The 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.

Redux is a good fit when...
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.

You probably do not need Redux when...
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 data

Use a server cache.

RTK Query / TanStack Query
Local state

One component, or a few close together.

useState, lifting state up
Rarely changing shared values

Theme, language, user, services.

React Context
Busy shared client state

Many readers and writers, needs tracing.

Redux
Record actions

A 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.

Audit a store

“Open a Redux app you know. For each slice, list which components read it. Which slices are used by only one screen?”

Replay it yourself

“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?”

Fix it properly

“Better still: delete count from the state and calculate it from items (Lesson 4). Does the bug still exist?”

Sort your app

“List ten pieces of state in an app you are building. Take each through the four questions in the diagram.”

What usually goes wrong

Adding Redux because the app is "big"

Size does not decide it - the kind of state does. A big app with mostly server data may need no Redux at all.

A slice for one screen’s form

If one component reads and changes it, it is local state. Use useState.

✗ createSlice({ name: "settingsPage", ... })   // used only by SettingsPage
✓ const [displayName, setDisplayName] = useState("");
Server data in hand-written slices

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 Query
Refusing Redux when the criteria fit

When 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.

1.

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 hint

Ask the four questions from the diagram, in order.

Show solution
Answer
(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)
2.

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 hint

Server data, one-screen state, one-component state.

Show solution
Answer
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.
3.

In replay.mjs, which recorded action first made the state wrong, and why?

Show hint

Look for the first line that says WRONG.

Show solution
Answer
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.
Coding challenge

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.

It should
  • 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 solution
One possible plan
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

Name two costs of Redux.
More code (a slice, actions, store, selectors), a larger bundle (about 10.6 KB gzip here), and concepts the whole team must learn.
Name two benefits of Redux.
One source of truth for shared state, traceable and replayable changes, selectors that limit re-renders, middleware, RTK Query.
A slice is read and changed only by the settings screen. What should you do?
Move it to useState in the settings screen and delete the slice.
Where should a list of products from an API live?
In a server cache such as RTK Query, not in a hand-written slice.
Does a big app automatically need Redux?
No. It depends on whether it has shared, frequently changing client state.

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. 1.

    Our settings form: lines of code and gzip size with Redux, and without?

  2. 2.

    How did replaying the recorded actions help find the cart bug?

  3. 3.

    Which of these needs Redux: the theme, a modal’s open state, the cart shared by many pages, the product list?

  4. 4.

    Why does this website not use Redux?

  5. 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...