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'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)
When Redux wins: Medium+ apps, multiple devs, complex async flows.
Your First "Aha!" Moment
After Redux: Any component reads useSelector(state => state.count). Change anywhere? Everyone sees instantly.
That's the power—one store, many readers, predictable updates.
