DEV Community

Cover image for Redux Explained — Why It Exists and How to Use It Simply
DHANRAJ S
DHANRAJ S

Posted on

Redux Explained — Why It Exists and How to Use It Simply

Hey!

Before we start — let me show you a situation.

You are building a medium-sized React app. You have a logged-in user. Their name shows in the Navbar. Their profile shows on the Profile page. Their orders show on the Orders page.

All three need the same user data.

So you put the user in the top-level component and pass it down through props.

It works. But then your app grows.

More components need the user. More props get passed. More levels get added. You already learned about useContext — and yes, it helps.

But now imagine this.

Your app has a shopping cart. A notification system. A theme setting. A logged-in user. All of this is global data. All of it needs to be accessible from many places. All of it can change at any time.

You have multiple contexts. Multiple providers wrapping each other. Managing all these state changes becomes messy. Debugging becomes harder. "Which context changed and why?" becomes a real question.

That is the problem Redux solves.


1. What Is Redux?

Redux is a state management library.

It gives you one central place to store all your app's data. One single source of truth. Every component reads from it. Every component updates through it.

Think of it like a government database.

Every department — health, transport, education — does not keep its own separate records. They all read from and write to one central database. One source. Always accurate. Always in sync.

That central database is your Redux store.


2. The Three Core Ideas in Redux

Redux is built on three simple ideas. Understand these and everything else makes sense.

Store — the single source of truth

The store holds your entire app's state in one object.

{
  user: { name: "Ravi", role: "admin" },
  cart: { items: [], total: 0 },
  theme: "dark"
}
Enter fullscreen mode Exit fullscreen mode

One object. Your whole app's state. Right there.

Action — describing what happened

An action is a plain object that describes what event just happened.

{ type: "ADD_TO_CART", payload: { id: 1, name: "Laptop", price: 50000 } }
{ type: "REMOVE_FROM_CART", payload: 1 }
{ type: "LOGOUT_USER" }
Enter fullscreen mode Exit fullscreen mode

type — what happened.
payload — any extra data needed.

You never change the store directly. You send an action and say "this happened."

Reducer — deciding what changes

The reducer is a function that takes the current state and an action — and returns the new state.

function cartReducer(state = { items: [] }, action) {
  switch (action.type) {
    case "ADD_TO_CART":
      return { items: [...state.items, action.payload] };
    case "REMOVE_FROM_CART":
      return { items: state.items.filter(item => item.id !== action.payload) };
    default:
      return state;
  }
}
Enter fullscreen mode Exit fullscreen mode

Sound familiar? Yes — same idea as useReducer. Redux takes this concept and makes it work at the app level.


3. The Redux Flow — How It All Connects

This is the part most beginners find confusing. Let us make it crystal clear.

User does something
      ↓
Component dispatches an Action
      ↓
Reducer receives Action + current State
      ↓
Reducer returns new State
      ↓
Store updates
      ↓
All components that need that data re-render
Enter fullscreen mode Exit fullscreen mode

One direction. Always. No shortcuts.

This is called unidirectional data flow. And it is what makes Redux so predictable and easy to debug.

Quick question for you.

Why do you think having one direction for data flow makes debugging easier?

Because you always know where to look. Something went wrong? Check what action was dispatched. Check what the reducer did with it. The trail is always clear. No guessing which component updated which state.


4. Setting Up Redux in a React App

Modern Redux uses Redux Toolkit — the official recommended way. It removes a lot of the old boilerplate.

Install it:

npm install @reduxjs/toolkit react-redux
Enter fullscreen mode Exit fullscreen mode

5. A Simple Example — Counter With Redux

Let us build the simplest possible Redux example. A counter.

Step 1 — Create a slice

A slice is a piece of your Redux store. It holds the state and the reducers for one feature.

// store/counterSlice.js
import { createSlice } from "@reduxjs/toolkit";

const counterSlice = createSlice({
  name: "counter",
  initialState: { count: 0 },
  reducers: {
    increment(state) {
      state.count += 1;
    },
    decrement(state) {
      state.count -= 1;
    },
    reset(state) {
      state.count = 0;
    }
  }
});

export const { increment, decrement, reset } = counterSlice.actions;
export default counterSlice.reducer;
Enter fullscreen mode Exit fullscreen mode

createSlice handles the action creators and reducer for you. No need to write them separately.

Step 2 — Create the store

// store/store.js
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "./counterSlice";

const store = configureStore({
  reducer: {
    counter: counterReducer
  }
});

export default store;
Enter fullscreen mode Exit fullscreen mode

Step 3 — Provide the store to your app

// main.jsx
import { Provider } from "react-redux";
import store from "./store/store";

ReactDOM.createRoot(document.getElementById("root")).render(
  <Provider store={store}>
    <App />
  </Provider>
);
Enter fullscreen mode Exit fullscreen mode

Provider makes the store available to every component inside it. Just like Context Provider — but for Redux.

Step 4 — Use it in a component

// Counter.jsx
import { useSelector, useDispatch } from "react-redux";
import { increment, decrement, reset } from "./store/counterSlice";

function Counter() {
  const count = useSelector(state => state.counter.count);
  const dispatch = useDispatch();

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => dispatch(increment())}>+1</button>
      <button onClick={() => dispatch(decrement())}>-1</button>
      <button onClick={() => dispatch(reset())}>Reset</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

useSelector — reads data from the store. Like saying "give me this piece of the store."

useDispatch — gives you the dispatch function. You use it to send actions to the store.

Quick question for you.

Can you use useSelector in two different components — and both will always show the same up-to-date value?

Yes. Because both are reading from the same store. When the store updates — both components re-render with the latest value automatically. That is the power of a single source of truth.


6. What Does the Full Flow Look Like?

Let us trace through clicking the +1 button:

User clicks +1
      ↓
dispatch(increment()) fires
      ↓
Redux sends action { type: "counter/increment" } to the reducer
      ↓
Reducer runs: state.count += 1
      ↓
Store updates: count is now 1
      ↓
Counter component re-renders
      ↓
Screen shows: Count: 1
Enter fullscreen mode Exit fullscreen mode

Every step is visible. Every step is traceable.


7. useContext vs Redux — Which One to Use?

This is the question everyone asks. Both manage global state. So when do you use which?

useContext Redux
Best for Simple global data — theme, user, language Complex state with many interactions
Setup Very simple More setup needed
Dev tools No Yes — powerful Redux DevTools
Scalability Gets messy at large scale Built for large apps
When state changes All context consumers re-render Only components that use that slice re-render

Simple rule.

Small app with simple global state — useContext is enough.

Large app with complex state, many features, many interactions — Redux is worth it.

Do not reach for Redux on day one. Start simple. Add Redux when you actually feel the pain of managing state without it.


8. Why Redux Is Still Worth Learning

You might be thinking — "this feels like a lot of setup for a counter."

Fair point. For a counter — it is overkill.

But Redux is used in large production apps for real reasons.

Redux DevTools let you time-travel through every state change. You can replay actions, inspect what changed, and debug issues in seconds.

Every state change has a clear action and a clear trail. No mystery updates. No wondering which component changed what.

Teams working on the same codebase can follow the same pattern. Everyone knows where the state is and how it changes.


Quick Summary — 5 Things to Remember

  1. Redux is a central store for all your app state — one place, one source of truth, every component reads from it.

  2. Actions describe what happened — you never change the store directly. You dispatch an action and let the reducer handle it.

  3. Reducers decide the new state — they take the current state and the action, and return what the state should look like next.

  4. useSelector reads from the store. useDispatch sends actions — these two hooks are how your components talk to Redux.

  5. Use Redux when your app is actually complex — for simple apps, useContext is enough. Redux shines when state management becomes genuinely hard.


Redux felt intimidating when it first came out. The old way had a lot of boilerplate. Redux Toolkit fixed most of that.

Today — setting up Redux is straightforward. And once you see the DevTools in action, time-travelling through state changes, you will understand why large teams rely on it.

Try the counter example. Add a new slice for a theme toggle. See how two completely separate slices live in the same store and work independently.

That is the moment Redux starts to make sense.

If you have a question — drop it in the comments below.


Thanks for reading. Keep building.

Top comments (0)