React + Redux

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.

Leave a Comment