Dec 20, 2025

Redux for Beginners: Store, Actions, Reducers Explained (React)

Tags

Remember when you had a simple React counter app with useState? Click button → count goes up. Perfect! Now imagine 10 components all needing that counter. Or a user profile that needs to sync across Header, Sidebar, and Profile pages. Suddenly, you're passing props 8 levels deep. That's "prop drilling hell"—and Redux was born to fix it.


I've been there. My first "serious" React app became a prop-passing nightmare. Redux felt intimidating at first, but once I got the three core pieces (Store, Actions, Reducers), everything clicked. Let's build your first store together—no prior knowledge needed!


The Problem Redux Solves (Real Example)


Without Redux (prop drilling mess):

App → Dashboard → Header → UserMenu → Profile → Settings → Notifications (8 props deep!)

With Redux (one global store):

Any component → dispatch action → store updates → any component reads fresh data


Visual flow:


redux-flow


Redux's Three Magic Pieces


1. Store = Your App's Single Source of Truth


Think of it as a giant JavaScript object holding all your app state:


{
  counter: 5,
  user: { name: 'Alice' },
  todos: [{ id: 1, text: 'Learn Redux' }]
}

2. Actions = "What happened?" Messages


Plain objects saying what changed:


{ type: 'counter/increment' }
// or
{ type: 'todos/addTodo', payload: { text: 'New task' } }


3. Reducers = "How does state change?" Rules


Pure functions that take current state + action → new state:


function counterReducer(state = 0, action) {
  switch(action.type) {
    case 'counter/increment':
      return state + 1;
    default:
      return state;
  }
}

Key rule: Reducers never mutate state—they always return new objects.



Your First Redux Store (Counter App)


Let's build a working counter. Create src/store.js:


// 1. Create store with initial state
import { createStore } from 'redux';

const initialState = { count: 0 };

function counterReducer(state = initialState, action) {
  switch(action.type) {
    case 'INCREMENT':
      return { ...state, count: state.count + 1 };
    case 'DECREMENT':
      return { ...state, count: state.count - 1 };
    case 'RESET':
      return initialState;
    default:
      return state;
  }
}

export const store = createStore(counterReducer);


Connect to React (src/index.js):


import React from 'react';
import { createRoot } from 'react-dom/client';
import { Provider } from 'react-redux';
import { store } from './store';
import App from './App';

const root = createRoot(document.getElementById('root'));
root.render(
  <Provider store={store}>
    <App />
  </Provider>
);


Use in component (src/App.js):


import { useSelector, useDispatch } from 'react-redux';

function App() {
  // Read from store
  const count = useSelector(state => state.count);
  
  // Send actions to store
  const dispatch = useDispatch();
  
  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
      <button onClick={() => dispatch({ type: 'DECREMENT' })}>-</button>
      <button onClick={() => dispatch({ type: 'RESET' })}>Reset</button>
    </div>
  );
}

Boom! Working Redux counter. Open Redux DevTools—you'll see actions flowing and state updating live .


Redux vs useState/useReducer (Quick Comparison)


Feature
useState
Redux
Scope
Component only
App-wide
DevTools
None
Time travel!
Teams
Solo fine
Multiple devs
Async
Manual
Built-in patterns
Scale
<10 components
100+ components

When useState wins: Simple apps, 1-3 components sharing state.
When Redux wins: Medium+ apps, multiple devs, complex async flows.

Your First "Aha!" Moment


Before Redux: Pass count through 5 components. Change in Profile? Header doesn't know.
After Redux: Any component reads useSelector(state => state.count). Change anywhere? Everyone sees instantly.

That's the power—one store, many readers, predictable updates.

Next: Part 2 shows Redux Toolkit (90% less code!) + real todo CRUD. You'll never write vanilla Redux again.


EmoticonEmoticon