Redux & State Management
As your React application grows beyond a few components, you’ll encounter challenges with state management. Props drilling becomes unwieldy, Context API re-renders too much, and tracking state changes becomes a nightmare. Redux addresses these problems with a structured approach to global state.
When Do You Need Redux?
Redux adds complexity, so it’s important to know when it’s worth it:
You Might Need Redux When:
- Multiple components need access to the same state
- State updates come from many different sources (user input, API calls, WebSockets)
- State changes are complex with many interconnected pieces
- Debugging is difficult because you can’t track what changed and when
- Team collaboration requires predictable patterns everyone follows
You Probably Don’t Need Redux When:
- Your app is simple with few components
- State is localized to individual components
- Context API handles your needs without performance issues
- You’re building a prototype or MVP
Start with React’s built-in state management (useState, useContext). Only add Redux when you feel genuine pain from state complexity.
Redux Core Concepts
Before diving into code, understand the philosophy:
| Concept | Description |
|---|
| Single Source of Truth | All app state lives in one store object |
| State is Read-Only | The only way to change state is by dispatching actions |
| Pure Reducers | Changes are made with pure functions (given same input, always same output) |
This predictability makes Redux apps easier to debug, test, and reason about.
We’ll use Redux Toolkit (RTK), the official, recommended way to write Redux logic. It eliminates boilerplate and includes best practices by default.
Installation
npm install @reduxjs/toolkit react-redux
1. Create a Slice
A “slice” contains the reducer logic and actions for a single feature.
features/counter/counterSlice.js
import { createSlice } from '@reduxjs/toolkit';
export const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0,
},
reducers: {
increment: (state) => {
// Redux Toolkit allows us to write "mutating" logic in reducers.
// It doesn't actually mutate the state because it uses the Immer library.
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
},
},
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;
Create the Redux store and add your slices.
app/store.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from '../features/counter/counterSlice';
export const store = configureStore({
reducer: {
counter: counterReducer,
},
});
3. Provide the Store
Wrap your application with the Provider.
main.jsx
import { store } from './app/store';
import { Provider } from 'react-redux';
ReactDOM.createRoot(document.getElementById('root')).render(
<Provider store={store}>
<App />
</Provider>
);
4. Use State and Dispatch
Use useSelector to read data and useDispatch to dispatch actions.
Counter.jsx
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from './features/counter/counterSlice';
export function Counter() {
const count = useSelector((state) => state.counter.value);
const dispatch = useDispatch();
return (
<div>
<button onClick={() => dispatch(increment())}>Increment</button>
<span>{count}</span>
<button onClick={() => dispatch(decrement())}>Decrement</button>
</div>
);
}
Async Logic (Thunks)
Redux Toolkit includes createAsyncThunk for handling async logic (like API calls).
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
export const fetchUser = createAsyncThunk(
'users/fetchById',
async (userId, thunkAPI) => {
const response = await fetch(\`/api/users/\${userId}\`);
return response.json();
}
);
const usersSlice = createSlice({
name: 'users',
initialState: { entities: [], loading: 'idle' },
extraReducers: (builder) => {
builder.addCase(fetchUser.fulfilled, (state, action) => {
state.entities.push(action.payload);
});
},
});
Other Options
While Redux is popular, other libraries exist:
- Zustand: Minimalist, hook-based state management.
- Recoil / Jotai: Atomic state management.
- TanStack Query (React Query): Best for server state (caching API responses).
Summary
- Redux Toolkit simplifies Redux setup.
- Slices organize logic by feature.
- Provider makes the store available to the app.
- useSelector reads state.
- useDispatch sends actions to update state.