What you'll learn
Quick Answer
Redux keeps shared state in one store outside your components. Components read from it and dispatch actions to change it, and a reducer produces the new state. Most small apps do not need it — useState and Context are usually enough.
The problem: prop drilling
State in React lives in a component and flows down through props. That works until two distant components need the same data.
The logged-in user is needed by the header, the sidebar and a settings page six levels deep. So the state goes in the top component and gets passed down through every intermediate component — none of which use it — purely to reach the ones that do. That is prop drilling.
It is not merely ugly. Every intermediate component now has a prop in its signature it does not care about, and changing the shape of that data means editing every file along the path.
Redux moves shared state out of the component tree entirely, into a store any component can read from directly.
Store, action, reducer
Three pieces and a one-way cycle:
- Store — one object holding the shared state.
- Action — a plain object describing something that happened:
{ type: "cart/itemAdded", payload: item }. - Reducer — a function taking the current state and an action, returning the new state.
The flow is always the same: a component dispatches an action, the reducer computes new state, components reading that state re-render.
The rule that makes it predictable is that reducers must be pure — same input, same output, no API calls, no random values, no mutating the existing state. Because every change goes through this one path, you can log every action and replay them, which is why Redux debugging tools are so good.
Redux Toolkit is the modern way
Older tutorials show action-type constants, action creators and switch statements across several files. That verbosity is the main reason Redux got its reputation. Redux Toolkit is now the official approach and removes most of it:
import { createSlice, configureStore } from "@reduxjs/toolkit";
const cartSlice = createSlice({
name: "cart",
initialState: { items: [] },
reducers: {
itemAdded(state, action) {
state.items.push(action.payload); // looks like mutation
},
cartCleared(state) {
state.items = [];
},
},
});
export const { itemAdded, cartCleared } = cartSlice.actions;
export const store = configureStore({ reducer: { cart: cartSlice.reducer } });
That state.items.push(...) appears to break the no-mutation rule. It does not — Toolkit uses a library that tracks the changes and produces a new immutable state from them. You write the simple version and still get immutability.
If you are learning Redux in 2026, learn Toolkit. The older pattern is worth recognising in existing codebases and is not worth writing fresh.
Using it in components
import { useSelector, useDispatch } from "react-redux";
import { itemAdded } from "./cartSlice";
function Cart() {
const items = useSelector((state) => state.cart.items);
const dispatch = useDispatch();
return (
<div>
<p>{items.length} items</p>
<button onClick={() => dispatch(itemAdded({ id: 1 }))}>Add</button>
</div>
);
}
useSelector reads a slice of the store and re-renders this component when that slice changes. Select as narrowly as possible — selecting the whole store means re-rendering on every unrelated change.
useDispatch sends actions. No props threaded through six components; any component can read and dispatch.
Do you actually need Redux?
Often not, and reaching for it too early is a common mistake.
Plain useState is enough when state belongs to one component or a parent and its immediate children. Most components never need more.
Context is enough for values that are shared widely but change rarely — theme, language, the current user. Context solves prop drilling on its own; it is weaker when the value changes frequently, because every consumer re-renders.
Redux earns its place when many components read and write the same frequently-changing state, when you need to trace how state changed over time, or when the team is large enough that an enforced pattern is worth the ceremony.
Also worth knowing: a large share of what people historically used Redux for was caching server data. Libraries built for that job — React Query and similar — handle caching, refetching and loading states far better. If your "global state" is mostly API responses, that is probably the tool you want.
For a college project, useState plus Context is usually the right answer, and being able to explain why you did not use Redux is a stronger interview answer than having used it unnecessarily. See React hooks explained for the foundations.
