React component state works fine until multiple components need the same data and prop drilling becomes unmanageable. Redux provides a single, predictable state container that any component can read from and dispatch actions to.
TL;DR: A React + Redux reference: store setup, actions, reducers, selectors, and Thunk middleware for async operations.
Stack: React, Redux, Redux Toolkit, React-Redux
Level: Intermediate
Reading time: ~18 min
The problem Redux solves
In a typical app, you have many components each with their own state. When components start depending on each other’s data, you end up passing props through layers of components that don’t even use them, and that’s prop drilling. Redux moves shared state out of components and into a central store, so any component can read or update it without a prop relay chain.
Setup
npx create-react-app my-app
yarn add redux react-redux immer
yarn add redux-devtools-extension
Reducer (store/modules/shop/reducer.js)
import produce from 'immer';
const INITIAL_STATE = { customer: {}, items: [] };
function shop(state = INITIAL_STATE, action) {
switch (action.type) {
case 'SET_CUSTOMER':
return produce(state, (draft) => {
draft.customer = action.customer;
});
case 'ADD_ITEM':
return produce(state, (draft) => {
draft.items.push(action.item);
});
default:
return state;
}
}
export default shop;
Root reducer and store (store/index.js)
import { combineReducers } from 'redux';
import shop from './modules/shop/reducer';
export default combineReducers({ shop });
// store/index.js
import { createStore } from 'redux';
import rootReducer from './modules/rootReducer';
const store = createStore(rootReducer, window.__REDUX_DEVTOOLS_EXTENSION__?.());
export default store;
Connect to the app (index.js)
import { Provider } from 'react-redux';
import store from './store';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
Dispatching actions
import { useDispatch } from 'react-redux';
const dispatch = useDispatch();
dispatch({ type: 'SET_CUSTOMER', customer: { name: 'Allan', age: 30 } });
Reading from the store
import { useSelector } from 'react-redux';
const customer = useSelector((state) => state.shop.customer);
const items = useSelector((state) => state.shop.items);
What you’ve built
A Redux setup with centralized state, actions, reducers using Immer for immutability, and hooks for reading and dispatching.
Next steps
- Use Redux Toolkit (createSlice, createAsyncThunk) instead of vanilla Redux. It eliminates most boilerplate and is the official recommended approach.
- Use memoized selectors with reselect to avoid unnecessary re-renders when reading from the store.
- Redux is overkill for many apps. For server state (API data), React Query or SWR is simpler, and for local UI state, useState and useContext are often enough.
Questions or feedback? Find me on LinkedIn or GitHub.