Why State Management Matters?
React’s built-in state management works well for small applications. However, as your app grows, multiple components need to share and modify the same data this leads to prop drilling, duplicate state logic, and complex debugging.
Redux solves this by creating a central store that holds all your app’s state in one place. Any component can access and update this state using actions and reducers, making your app more predictable and easier to scale.
How Redux Works Internally (Quick Theory)
Redux revolves around four main concepts:
- Store: The global state container.
- Actions: Plain JavaScript objects describing what happened.
- Reducers: Pure functions that return the new state based on the current state and the action.
- Dispatch: A method used to send actions to the reducer.
Redux Toolkit simplifies all of these into concise syntax using the createSlice
and configureStore
methods.
Getting Started with Redux in a React App
Step 1. Set up Your React Project
npx create-react-app redux-counter-app
cd redux-counter-app
npm install @reduxjs/toolkit react-redux
We’re using Redux Toolkit, which is the official, modern way to write Redux logic. It reduces boilerplate code and adds helpful defaults. react-redux
is the library that connects your React components to the Redux store.
Project Structure
redux-counter-app/
│
├── src/
│ ├── app/
│ │ └── store.js # Redux store config
│ ├── features/
│ │ └── counter/
│ │ ├── counterSlice.js # Redux slice for counter state
│ ├── App.js
│ ├── index.js
We follow a feature-based folder structure. Each "slice" represents a feature with its own reducer, actions, and state. The store.js
combines all the slices into a single store.
Step 2. Create a Redux Slice
// src/features/counter/counterSlice.js
import { createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: (state) => { state.value += 1 },
decrement: (state) => { state.value -= 1 },
reset: (state) => { state.value = 0 }
}
});
export const { increment, decrement, reset } = counterSlice.actions;
export default counterSlice.reducer;
Here, we’re using createSlice()
to generate:
-
the state (value
)
-
the reducer functions (increment
, decrement
, reset
)
-
And the corresponding action creators are automatically.
Thanks to Redux Toolkit's use of Immer.js, you can write “mutating” logic like state.value += 1
, even though the state is still being updated immutably under the hood.
Step 3. Configure the Redux Store
// src/app/store.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from '../features/counter/counterSlice';
export const store = configureStore({
reducer: {
counter: counterReducer,
}
});
The configureStore()
method combines all your slices into a single Redux store. Here, we only have one slice — counter
. Later, as your app grows, you can add more slices like auth
, products
, cart
, etc.
Step 4. Provide the Store to the React App
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { Provider } from 'react-redux';
import { store } from './app/store';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<App />
</Provider>
);
Wrapping your app with <Provider>
gives all your components access to the Redux store using React context under the hood. This is essential for connecting components to the global state.
Step 5. Use Redux in a Component
// src/App.js
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement, reset } from './features/counter/counterSlice';
function App() {
const count = useSelector((state) => state.counter.value);
const dispatch = useDispatch();
return (
<div style={{ textAlign: 'center', marginTop: '4rem' }}>
<h1>Redux Counter App</h1>
<h2>{count}</h2>
<button onClick={() => dispatch(increment())}>+ Increment</button>
<button onClick={() => dispatch(decrement())}>- Decrement</button>
<button onClick={() => dispatch(reset())}>Reset</button>
</div>
);
}
export default App;
useSelector()
reads data from the Redux store.
useDispatch()
sends actions to the store.
- When an action is dispatched, the corresponding reducer runs and updates the store, triggering a re-render.
When to Use Redux?
Use Redux when
- You have a global/shared state across many components (e.g., user data, cart items).
- You want centralized logic for debugging and testing.
- You are building large or scalable apps (e.g., e-commerce, admin dashboards).
- You need to persist or sync state with APIs or localStorage.
When Redux is Overkill
If your app is small or the state is only needed in a few components, React's built-in state (useState
, useContext
, or useReducer
) may be enough. Don’t use Redux just because it’s popular, use it when your app’s complexity demands it.
Conclusion
Redux is one of the most powerful tools in a React developer’s toolbox. With Redux Toolkit, managing state becomes more efficient, readable, and scalable.
If you're building apps where state needs to be shared, tracked, or debugged, Redux is a great fit. It brings structure to chaos and keeps your state logic predictable and centralized.