Redux is more than a state management library. It is a predictable architecture for managing application state through explicit actions, pure state transitions, and a centralized store.
If you have worked with React, Angular, or modern frontend applications, you have probably encountered problems such as:
- Multiple components needing the same state
- State being passed through many layers of components
- Difficult-to-debug state changes
- Complex asynchronous operations
- Inconsistent application state
- Business logic scattered across components
Redux was designed to solve these problems by introducing a predictable and structured approach to state management.
In this article, we will go from the fundamentals to practical Redux development, including:
- What Redux is
- Why Redux exists
- Core Redux concepts
- Store
- Actions
- Reducers
- Dispatch
- Selectors
- Immutability
- Redux data flow
- Middleware
- Async operations
- Redux Toolkit
- RTK Query
- Entity management
- Real-world architecture
- Common mistakes
- A complete example
1. What Is Redux?
Redux is a predictable state management library.
The basic idea is simple:
UI
↓
Dispatch Action
↓
Reducer
↓
New State
↓
Store
↓
UI Updates
Enter fullscreen mode Exit fullscreen mode
Instead of allowing components to modify application state however they want, Redux creates a controlled flow.
For example:
dispatch({
type: "counter/increment"
});
Enter fullscreen mode Exit fullscreen mode
The action reaches a reducer:
function counterReducer(state, action) {
if (action.type === "counter/increment") {
return {
...state,
value: state.value + 1
};
}
return state;
}
Enter fullscreen mode Exit fullscreen mode
The reducer produces the next state.
2. Why Do We Need Redux?
Consider a large application.
You may have:
App
├── Header
│ └── UserMenu
│ └── UserProfile
│
├── Dashboard
│ ├── Statistics
│ ├── Orders
│ └── Notifications
│
└── Sidebar
Enter fullscreen mode Exit fullscreen mode
Suppose the logged-in user is needed by:
- Header
- UserMenu
- Dashboard
- Sidebar
- Notifications
Without centralized state management, you may end up passing:
App
↓
Header
↓
UserMenu
↓
UserProfile
Enter fullscreen mode Exit fullscreen mode
This is commonly called prop drilling.
Redux provides a centralized store:
Redux Store
/ | \
↓ ↓ ↓
Header Dashboard Sidebar
Enter fullscreen mode Exit fullscreen mode
Components can subscribe to the state they need.
3. Redux Core Principles
Redux is based on several important principles.
3.1 Single Source of Truth
Application state is stored in one centralized store.
{
user: {
id: 1,
name: "Abanoub"
},
cart: {
items: []
},
products: [],
ui: {
theme: "dark"
}
}
Enter fullscreen mode Exit fullscreen mode
Instead of having unrelated copies of important state throughout the application, Redux provides a central source.
4. State Is Read-Only
Components should not directly modify Redux state.
Incorrect:
state.counter.value++;
Enter fullscreen mode Exit fullscreen mode
Instead, dispatch an action:
dispatch({
type: "counter/increment"
});
Enter fullscreen mode Exit fullscreen mode
The reducer determines how the state changes.
5. Changes Are Made Through Pure Functions
Reducers are responsible for calculating the next state.
Conceptually:
Previous State + Action = Next State
Enter fullscreen mode Exit fullscreen mode
Example:
const previousState = {
value: 10
};
const action = {
type: "increment"
};
const nextState = {
value: 11
};
Enter fullscreen mode Exit fullscreen mode
The reducer:
function reducer(state, action) {
switch (action.type) {
case "increment":
return {
...state,
value: state.value + 1
};
default:
return state;
}
}
Enter fullscreen mode Exit fullscreen mode
6. The Redux Store
The store contains the application state.
With modern Redux, the recommended approach is Redux Toolkit.
import { configureStore } from "@reduxjs/toolkit";
const store = configureStore({
reducer: {
counter: counterReducer
}
});
Enter fullscreen mode Exit fullscreen mode
Conceptually:
Store
│
├── counter
├── user
├── products
├── cart
└── notifications
Enter fullscreen mode Exit fullscreen mode
7. Actions
An action describes what happened.
Example:
{
type: "counter/increment"
}
Enter fullscreen mode Exit fullscreen mode
Another example:
{
type: "cart/addItem",
payload: {
id: 10,
name: "Keyboard"
}
}
Enter fullscreen mode Exit fullscreen mode
The action does not directly modify state.
It describes an event.
8. Action Types
An action type is usually a string.
{
type: "user/login"
}
Enter fullscreen mode Exit fullscreen mode
Examples:
user/login
user/logout
cart/addItem
cart/removeItem
products/load
products/delete
Enter fullscreen mode Exit fullscreen mode
A useful naming convention is:
feature/event
Enter fullscreen mode Exit fullscreen mode
For example:
cart/addItem
Enter fullscreen mode Exit fullscreen mode
9. Payload
The payload contains additional information.
{
type: "cart/addItem",
payload: {
id: 1,
name: "Laptop",
price: 1200
}
}
Enter fullscreen mode Exit fullscreen mode
Another example:
{
type: "user/setUser",
payload: {
id: 5,
name: "John"
}
}
Enter fullscreen mode Exit fullscreen mode
10. Reducers
A reducer receives:
Current State
+
Action
Enter fullscreen mode Exit fullscreen mode
and returns:
Next State
Enter fullscreen mode Exit fullscreen mode
Example:
function counterReducer(state = { value: 0 }, action) {
switch (action.type) {
case "increment":
return {
...state,
value: state.value + 1
};
case "decrement":
return {
...state,
value: state.value - 1
};
default:
return state;
}
}
Enter fullscreen mode Exit fullscreen mode
A reducer should be:
- Predictable
- Pure
- Deterministic
- Free of side effects
11. What Does “Pure Function” Mean?
A pure function:
- Produces the same output for the same input.
- Does not modify external state.
- Does not perform side effects.
Example:
function add(a, b) {
return a + b;
}
Enter fullscreen mode Exit fullscreen mode
This is pure.
But:
let total = 0;
function add(value) {
total += value;
}
Enter fullscreen mode Exit fullscreen mode
This is not pure because it modifies external state.
Reducers should follow the pure-function principle.
12. Dispatch
Dispatch sends an action to Redux.
dispatch({
type: "counter/increment"
});
Enter fullscreen mode Exit fullscreen mode
The flow becomes:
Component
↓
dispatch(action)
↓
Redux
↓
Reducer
↓
New State
↓
Store
↓
Subscribed Components
Enter fullscreen mode Exit fullscreen mode
13. Selectors
Selectors read data from the Redux store.
For example:
const selectCount = state => state.counter.value;
Enter fullscreen mode Exit fullscreen mode
Then:
const count = useSelector(selectCount);
Enter fullscreen mode Exit fullscreen mode
Selectors help keep components independent from the exact shape of the state.
Instead of:
state.counter.value
Enter fullscreen mode Exit fullscreen mode
everywhere, you can use:
selectCount(state)
Enter fullscreen mode Exit fullscreen mode
14. Redux Data Flow
Redux follows a predictable one-way data flow.
┌─────────────┐
│ UI │
└──────┬──────┘
│
│ dispatch()
↓
┌─────────────┐
│ Action │
└──────┬──────┘
↓
┌─────────────┐
│ Reducer │
└──────┬──────┘
↓
┌─────────────┐
│ Store │
└──────┬──────┘
↓
┌─────────────┐
│ UI │
└─────────────┘
Enter fullscreen mode Exit fullscreen mode
This predictable flow is one of Redux’s biggest advantages.
15. A Simple Redux Example
Let’s create a counter.
With Redux Toolkit:
import { createSlice, configureStore } from "@reduxjs/toolkit";
const counterSlice = createSlice({
name: "counter",
initialState: {
value: 0
},
reducers: {
increment(state) {
state.value += 1;
},
decrement(state) {
state.value -= 1;
},
incrementByAmount(state, action) {
state.value += action.payload;
}
}
});
Enter fullscreen mode Exit fullscreen mode
Export the actions:
export const {
increment,
decrement,
incrementByAmount
} = counterSlice.actions;
Enter fullscreen mode Exit fullscreen mode
Create the store:
const store = configureStore({
reducer: {
counter: counterSlice.reducer
}
});
Enter fullscreen mode Exit fullscreen mode
Now:
store.dispatch(increment());
Enter fullscreen mode Exit fullscreen mode
Or:
store.dispatch(incrementByAmount(10));
Enter fullscreen mode Exit fullscreen mode
16. Why Does Redux Toolkit Allow Mutation?
You might notice:
state.value += 1;
Enter fullscreen mode Exit fullscreen mode
Earlier we said Redux state should not be mutated.
So why does this work?
Redux Toolkit uses Immer internally.
Immer allows you to write:
state.value += 1;
Enter fullscreen mode Exit fullscreen mode
while internally producing an immutable state update.
Conceptually:
Your Code
↓
Immer
↓
Immutable Update
↓
Redux State
Enter fullscreen mode Exit fullscreen mode
This gives developers simpler syntax while preserving Redux’s immutability model.
17. createSlice
createSlice() is one of the most important Redux Toolkit APIs.
It combines:
- State
- Reducers
- Action creators
- Action types
Instead of manually writing:
const INCREMENT = "counter/increment";
function increment() {
return {
type: INCREMENT
};
}
function reducer(state, action) {
...
}
Enter fullscreen mode Exit fullscreen mode
you can write:
const counterSlice = createSlice({
name: "counter",
initialState: {
value: 0
},
reducers: {
increment(state) {
state.value++;
}
}
});
Enter fullscreen mode Exit fullscreen mode
Redux Toolkit generates the action creator automatically.
18. Payload Actions
Suppose we want to add a product.
const cartSlice = createSlice({
name: "cart",
initialState: {
items: []
},
reducers: {
addItem(state, action) {
state.items.push(action.payload);
}
}
});
Enter fullscreen mode Exit fullscreen mode
Dispatch:
dispatch(
addItem({
id: 1,
name: "Laptop",
price: 1200
})
);
Enter fullscreen mode Exit fullscreen mode
The action becomes conceptually:
{
type: "cart/addItem",
payload: {
id: 1,
name: "Laptop",
price: 1200
}
}
Enter fullscreen mode Exit fullscreen mode
19. Redux With React
Redux itself is independent of React.
To integrate Redux with React, we commonly use React-Redux.
First create the store:
const store = configureStore({
reducer: {
counter: counterReducer
}
});
Enter fullscreen mode Exit fullscreen mode
Then provide it to React:
import { Provider } from "react-redux";
<Provider store={store}>
<App />
</Provider>
Enter fullscreen mode Exit fullscreen mode
Now components can access Redux.
20. useSelector
useSelector() reads data.
import { useSelector } from "react-redux";
function Counter() {
const count = useSelector(
state => state.counter.value
);
return <h1>{count}</h1>;
}
Enter fullscreen mode Exit fullscreen mode
When the selected state changes, the component can re-render.
21. useDispatch
useDispatch() allows a component to dispatch actions.
import { useDispatch } from "react-redux";
import { increment } from "./counterSlice";
function CounterButton() {
const dispatch = useDispatch();
return (
<button onClick={() => dispatch(increment())}>
Increment
</button>
);
}
Enter fullscreen mode Exit fullscreen mode
22. Complete React + Redux Example
function Counter() {
const count = useSelector(
state => state.counter.value
);
const dispatch = useDispatch();
return (
<div>
<h1>{count}</h1>
<button
onClick={() => dispatch(increment())}
>
+
</button>
<button
onClick={() => dispatch(decrement())}
>
-
</button>
</div>
);
}
Enter fullscreen mode Exit fullscreen mode
The component does not directly change:
state.counter.value
Enter fullscreen mode Exit fullscreen mode
Instead:
Button
↓
dispatch(increment())
↓
Reducer
↓
New State
↓
useSelector
↓
Component Re-render
Enter fullscreen mode Exit fullscreen mode
23. Local State vs Redux State
Not every piece of state belongs in Redux.
For example:
const [isOpen, setIsOpen] = useState(false);
Enter fullscreen mode Exit fullscreen mode
This is usually local UI state.
Redux is more appropriate for state that needs to be shared or coordinated across different parts of an application.
Local State
Examples:
Modal open/closed
Input value
Dropdown state
Temporary UI state
Enter fullscreen mode Exit fullscreen mode
Global State
Examples:
Authenticated user
Shopping cart
Permissions
Global notifications
Shared application configuration
Cached server data
Enter fullscreen mode Exit fullscreen mode
24. Redux Is Not Always Necessary
A common mistake is:
“Every React application should use Redux.”
Not true.
For a small application:
React
+
useState
+
useContext
Enter fullscreen mode Exit fullscreen mode
may be enough.
Redux becomes more valuable as state complexity increases.
A useful question is:
Is the complexity of shared state becoming harder to manage than the complexity of introducing Redux?
25. Middleware
Middleware sits between:
dispatch()
↓
Middleware
↓
Reducer
Enter fullscreen mode Exit fullscreen mode
It can:
- Log actions
- Perform asynchronous operations
- Dispatch additional actions
- Handle side effects
- Integrate external services
Conceptually:
dispatch(action)
↓
Middleware
↓
Reducer
↓
Store
Enter fullscreen mode Exit fullscreen mode
26. Why Do We Need Middleware?
Reducers should be pure.
Therefore, things like:
fetch()
Enter fullscreen mode Exit fullscreen mode
should not normally happen inside reducers.
Incorrect:
function reducer(state, action) {
fetch("/api/products");
return state;
}
Enter fullscreen mode Exit fullscreen mode
Instead, asynchronous work should happen outside reducers, commonly through middleware.
27. Redux Thunk
Redux Toolkit includes thunk middleware by default.
A thunk allows you to dispatch a function-like async workflow.
Example:
const fetchUsers = () => async dispatch => {
dispatch(usersLoading());
try {
const response = await fetch("/api/users");
const users = await response.json();
dispatch(usersLoaded(users));
} catch (error) {
dispatch(usersFailed(error.message));
}
};
Enter fullscreen mode Exit fullscreen mode
Then:
dispatch(fetchUsers());
Enter fullscreen mode Exit fullscreen mode
The flow becomes:
Component
↓
dispatch(fetchUsers())
↓
Thunk
↓
API Request
↓
dispatch(usersLoaded())
↓
Reducer
↓
Store
Enter fullscreen mode Exit fullscreen mode
28. createAsyncThunk
Redux Toolkit provides createAsyncThunk() to simplify common async workflows.
export const fetchUsers = createAsyncThunk(
"users/fetchUsers",
async () => {
const response = await fetch("/api/users");
return response.json();
}
);
Enter fullscreen mode Exit fullscreen mode
Then handle the lifecycle:
const usersSlice = createSlice({
name: "users",
initialState: {
data: [],
loading: false,
error: null
},
extraReducers: builder => {
builder
.addCase(fetchUsers.pending, state => {
state.loading = true;
})
.addCase(fetchUsers.fulfilled, (state, action) => {
state.loading = false;
state.data = action.payload;
})
.addCase(fetchUsers.rejected, (state, action) => {
state.loading = false;
state.error = action.error.message;
});
}
});
Enter fullscreen mode Exit fullscreen mode
Now Redux automatically provides:
pending
fulfilled
rejected
Enter fullscreen mode Exit fullscreen mode
29. Handling API State
A common pattern is:
{
data: [],
loading: false,
error: null
}
Enter fullscreen mode Exit fullscreen mode
The state transitions are:
Initial
↓
loading = true
↓
API Request
↓
┌───────────────┐
│ │
Success Failure
│ │
↓ ↓
data error
Enter fullscreen mode Exit fullscreen mode
This makes asynchronous state explicit.
30. RTK Query
For server data, Redux Toolkit provides RTK Query.
RTK Query is designed specifically for:
- Fetching data
- Caching
- Refetching
- Loading states
- Error states
- Request deduplication
- Cache invalidation
Example:
const api = createApi({
reducerPath: "api",
baseQuery: fetchBaseQuery({
baseUrl: "/api"
}),
endpoints: builder => ({
getUsers: builder.query({
query: () => "/users"
})
})
});
Enter fullscreen mode Exit fullscreen mode
Then:
const {
data,
isLoading,
error
} = useGetUsersQuery();
Enter fullscreen mode Exit fullscreen mode
This removes a lot of manual async-state management.
31. Redux Toolkit vs Traditional Redux
Traditional Redux often required:
Action Types
Action Creators
Reducers
Store
Middleware
Selectors
Enter fullscreen mode Exit fullscreen mode
with a lot of boilerplate.
Modern Redux recommends Redux Toolkit.
Instead of:
const INCREMENT = "INCREMENT";
const increment = () => ({
type: INCREMENT
});
function reducer(state, action) {
switch (action.type) {
...
}
}
Enter fullscreen mode Exit fullscreen mode
you can use:
const counterSlice = createSlice({
name: "counter",
initialState: {
value: 0
},
reducers: {
increment(state) {
state.value++;
}
}
});
Enter fullscreen mode Exit fullscreen mode
Redux Toolkit is now the recommended way to write Redux applications.
32. Normalizing State
Suppose you have:
{
users: [
{
id: 1,
name: "John"
},
{
id: 2,
name: "Sarah"
}
]
}
Enter fullscreen mode Exit fullscreen mode
As applications grow, searching and updating entities can become inefficient.
Normalized state can look like:
{
users: {
ids: [1, 2],
entities: {
1: {
id: 1,
name: "John"
},
2: {
id: 2,
name: "Sarah"
}
}
}
}
Enter fullscreen mode Exit fullscreen mode
Redux Toolkit provides:
createEntityAdapter()
Enter fullscreen mode Exit fullscreen mode
for this use case.
33. createEntityAdapter
Example:
const usersAdapter = createEntityAdapter();
const initialState =
usersAdapter.getInitialState();
Enter fullscreen mode Exit fullscreen mode
You can then use generated reducers and selectors to manage entities efficiently.
This is especially useful for:
Users
Products
Orders
Messages
Notifications
Enter fullscreen mode Exit fullscreen mode
34. Selectors in Large Applications
Instead of exposing state structure everywhere:
state.products.entities
Enter fullscreen mode Exit fullscreen mode
create selectors:
const selectProducts =
state => state.products.entities;
Enter fullscreen mode Exit fullscreen mode
Then components use:
const products = useSelector(selectProducts);
Enter fullscreen mode Exit fullscreen mode
This improves maintainability.
35. Derived State
Sometimes you don’t need to store everything.
For example, suppose Redux contains:
{
products: [
{ price: 100 },
{ price: 200 },
{ price: 300 }
]
}
Enter fullscreen mode Exit fullscreen mode
You don’t necessarily need:
{
products: [...],
totalPrice: 600
}
Enter fullscreen mode Exit fullscreen mode
You can derive it:
const selectTotalPrice = state =>
state.products.reduce(
(total, product) => total + product.price,
0
);
Enter fullscreen mode Exit fullscreen mode
This avoids duplicated state.
36. Memoized Selectors
For expensive calculations, selectors can be memoized.
Redux Toolkit works well with Reselect-style selectors.
Example:
const selectCompletedTodos = createSelector(
[selectTodos],
todos =>
todos.filter(todo => todo.completed)
);
Enter fullscreen mode Exit fullscreen mode
The selector can avoid recalculating when its inputs haven’t changed.
37. Redux Architecture
A scalable Redux application can be organized by features:
src/
│
├── app/
│ └── store.js
│
├── features/
│ ├── auth/
│ │ ├── authSlice.js
│ │ ├── authSelectors.js
│ │ └── authApi.js
│ │
│ ├── products/
│ │ ├── productsSlice.js
│ │ ├── productsSelectors.js
│ │ └── productsApi.js
│ │
│ └── cart/
│ ├── cartSlice.js
│ └── cartSelectors.js
│
└── components/
Enter fullscreen mode Exit fullscreen mode
This is called feature-based organization.
It scales much better than organizing everything by technical type.
38. Real-World Example: E-Commerce
Imagine an e-commerce application.
We might have:
Redux Store
│
├── auth
│
├── cart
│
├── products
│
├── orders
│
└── ui
Enter fullscreen mode Exit fullscreen mode
Example:
{
auth: {
user: null,
token: null
},
cart: {
items: []
},
products: {
ids: [],
entities: {}
},
orders: {
ids: [],
entities: {}
},
ui: {
sidebarOpen: false
}
}
Enter fullscreen mode Exit fullscreen mode
39. Adding a Product to the Cart
User clicks:
Add to Cart
Enter fullscreen mode Exit fullscreen mode
The component dispatches:
dispatch(
addToCart({
productId: 10,
quantity: 1
})
);
Enter fullscreen mode Exit fullscreen mode
Redux flow:
Product Component
↓
addToCart()
↓
Action
↓
Cart Reducer
↓
Cart State Updated
↓
Cart Icon
↓
UI Updated
Enter fullscreen mode Exit fullscreen mode
The cart icon can now select:
const itemCount = useSelector(
selectCartItemCount
);
Enter fullscreen mode Exit fullscreen mode
40. Authentication Example
Suppose a user logs in.
The UI dispatches:
dispatch(
loginSuccess({
id: 1,
name: "John"
})
);
Enter fullscreen mode Exit fullscreen mode
The reducer updates:
{
user: {
id: 1,
name: "John"
},
isAuthenticated: true
}
Enter fullscreen mode Exit fullscreen mode
Other components can react to this state.
For example:
const user = useSelector(selectCurrentUser);
Enter fullscreen mode Exit fullscreen mode
41. Redux DevTools
One of Redux’s biggest advantages is debugging.
Redux DevTools can show:
Action
↓
Previous State
↓
Action Payload
↓
Next State
Enter fullscreen mode Exit fullscreen mode
For example:
cart/addItem
Enter fullscreen mode Exit fullscreen mode
You can inspect:
{
productId: 10,
quantity: 2
}
Enter fullscreen mode Exit fullscreen mode
and then compare the previous and next state.
This makes complex state transitions easier to debug.
42. Time-Travel Debugging
Because Redux state transitions are explicit:
State 0
↓
Action A
↓
State 1
↓
Action B
↓
State 2
↓
Action C
↓
State 3
Enter fullscreen mode Exit fullscreen mode
development tools can replay state transitions.
This is one of the conceptual reasons Redux became popular.
43. Common Redux Mistakes
Mistake 1: Putting Everything in Redux
Don’t put every UI detail into Redux.
Avoid:
{
modalIsOpen: true,
inputValue: "hello",
hoverState: true
}
Enter fullscreen mode Exit fullscreen mode
unless there is a genuine reason for global access.
Mistake 2: Mutating State Outside Immer
Do not do:
const user = store.getState().user;
user.name = "New Name";
Enter fullscreen mode Exit fullscreen mode
State should be changed through Redux actions and reducers.
Mistake 3: Putting API Calls in Reducers
Avoid:
function reducer(state, action) {
fetch("/api/users");
return state;
}
Enter fullscreen mode Exit fullscreen mode
Reducers should remain pure.
Mistake 4: Duplicating Derived Data
Avoid storing:
{
items: [...],
itemCount: 5
}
Enter fullscreen mode Exit fullscreen mode
if itemCount can simply be calculated from items.
Mistake 5: Huge Global Slice
Avoid creating:
appSlice.js
Enter fullscreen mode Exit fullscreen mode
with hundreds of unrelated responsibilities.
Prefer:
authSlice
cartSlice
productsSlice
ordersSlice
notificationsSlice
Enter fullscreen mode Exit fullscreen mode
44. Redux vs Context API
React Context and Redux solve related but different problems.
Context
Good for:
Theme
Locale
Authentication context
Simple global configuration
Enter fullscreen mode Exit fullscreen mode
Redux
Useful when you have:
Complex state
Many state transitions
Multiple consumers
Complex async workflows
Need for powerful debugging
Normalized entities
Large application state
Enter fullscreen mode Exit fullscreen mode
Context is not automatically a replacement for Redux.
45. Redux vs useState
useState is excellent for local state.
const [count, setCount] = useState(0);
Enter fullscreen mode Exit fullscreen mode
Redux becomes useful when state needs to be shared and the update logic becomes complex.
Think:
Simple local state
↓
useState
Enter fullscreen mode Exit fullscreen mode
versus:
Complex shared application state
↓
Redux
Enter fullscreen mode Exit fullscreen mode
46. Redux vs Zustand
Redux and Zustand are both state management solutions.
Redux provides a more structured architecture:
Actions
↓
Reducers
↓
Store
Enter fullscreen mode Exit fullscreen mode
Zustand generally provides a simpler API with less ceremony.
Redux is often preferred when:
- The application is large
- A standardized architecture matters
- Teams need predictable conventions
- Advanced Redux tooling is valuable
- Existing ecosystem integrations are important
Zustand can be attractive when simplicity and minimal boilerplate are priorities.
47. A Practical Redux Mental Model
If you remember only one model, remember this:
STATE
↑
REDUCER
↑
ACTION
↑
DISPATCH
↑
UI
Enter fullscreen mode Exit fullscreen mode
Or:
User Interaction
↓
Action
↓
Reducer
↓
New State
↓
UI
Enter fullscreen mode Exit fullscreen mode
48. Complete Mini Project
Let’s combine the concepts.
Store
import { configureStore } from "@reduxjs/toolkit";
import cartReducer from "./cartSlice";
export const store = configureStore({
reducer: {
cart: cartReducer
}
});
Enter fullscreen mode Exit fullscreen mode
Slice
import { createSlice } from "@reduxjs/toolkit";
const cartSlice = createSlice({
name: "cart",
initialState: {
items: []
},
reducers: {
addItem(state, action) {
const existingItem = state.items.find(
item => item.id === action.payload.id
);
if (existingItem) {
existingItem.quantity += 1;
} else {
state.items.push({
...action.payload,
quantity: 1
});
}
},
removeItem(state, action) {
state.items = state.items.filter(
item => item.id !== action.payload
);
},
clearCart(state) {
state.items = [];
}
}
});
export const {
addItem,
removeItem,
clearCart
} = cartSlice.actions;
export default cartSlice.reducer;
Enter fullscreen mode Exit fullscreen mode
Selector
export const selectCartItems =
state => state.cart.items;
export const selectCartCount =
state =>
state.cart.items.reduce(
(total, item) => total + item.quantity,
0
);
Enter fullscreen mode Exit fullscreen mode
Component
function Cart() {
const items = useSelector(selectCartItems);
const count = useSelector(selectCartCount);
const dispatch = useDispatch();
return (
<div>
<h1>Cart ({count})</h1>
{items.map(item => (
<div key={item.id}>
{item.name}
<button
onClick={() =>
dispatch(removeItem(item.id))
}
>
Remove
</button>
</div>
))}
<button
onClick={() => dispatch(clearCart())}
>
Clear Cart
</button>
</div>
);
}
Enter fullscreen mode Exit fullscreen mode
This is already a realistic Redux pattern.
49. The Most Important Redux APIs
Modern Redux development commonly revolves around:
configureStore()
createSlice()
createAsyncThunk()
createEntityAdapter()
createSelector()
createApi()
fetchBaseQuery()
Enter fullscreen mode Exit fullscreen mode
React integration commonly uses:
Provider
useSelector()
useDispatch()
Enter fullscreen mode Exit fullscreen mode
You do not need to memorize everything immediately.
Understand the architecture first.
50. Redux in One Diagram
┌──────────────┐
│ UI │
└──────┬───────┘
│
dispatch(action)
│
↓
┌──────────────┐
│ Middleware │
└──────┬───────┘
│
↓
┌──────────────┐
│ Reducer │
└──────┬───────┘
│
New Immutable State
│
↓
┌──────────────┐
│ Store │
└──────┬───────┘
│
subscribe/select
│
↓
┌──────────────┐
│ UI │
└──────────────┘
Enter fullscreen mode Exit fullscreen mode
Conclusion
Redux is fundamentally about predictable state transitions.
The most important concepts are:
Store
Actions
Reducers
Dispatch
Selectors
Middleware
Immutability
Enter fullscreen mode Exit fullscreen mode
Modern Redux development should generally use Redux Toolkit rather than manually writing the older Redux boilerplate.
The key mental model is:
UI
↓
dispatch(Action)
↓
Middleware
↓
Reducer
↓
New State
↓
Store
↓
Selectors
↓
UI
Enter fullscreen mode Exit fullscreen mode
Once you understand this flow, advanced Redux concepts such as asynchronous actions, RTK Query, entity adapters, memoized selectors, and large-scale Redux architecture become much easier to understand.
Redux isn’t simply a place to put variables.
It is an architecture for making state changes explicit, predictable, traceable, and easier to manage as an application grows.
