---
source_hash: "08946edc"
title: "Chapter 3: Redux and Redux Toolkit"
weight: 470
---

# Chapter 3: Redux and Redux Toolkit

## Why Redux?

Context is good for simple data (theme, user). But for complex applications:

- State shared between many components
- Complex update logic
- Need for action history (debug)
- Performance with many updates

Redux provides a structured and predictable solution.

## Fundamental Concepts

### 1. Store

The **store** is the single source of truth. All application state is in one object.

```javascript
{
    user: { id: 1, name: 'Jean' },
    products: [...],
    cart: { items: [], total: 0 },
    ui: { isLoading: false, error: null }
}
```

### 2. Actions

**Actions** describe what happened. They are objects with a `type`:

```javascript
{ type: 'cart/addItem', payload: { id: 1, name: 'Product', price: 29.99 } }
{ type: 'user/login', payload: { id: 1, name: 'Jean' } }
{ type: 'ui/setLoading', payload: true }
```

### 3. Reducers

**Reducers** specify how the state changes in response to an action:

```javascript
function cartReducer(state, action) {
    switch (action.type) {
        case 'cart/addItem':
            return {
                ...state,
                items: [...state.items, action.payload]
            };
        case 'cart/removeItem':
            return {
                ...state,
                items: state.items.filter(item => item.id !== action.payload)
            };
        default:
            return state;
    }
}
```

### The Redux Flow

```
Action -> Reducer -> New State -> UI updated
```

1. An event triggers an action
2. The reducer calculates the new state
3. The store notifies components
4. Components re-render

## Redux Toolkit: Redux Simplified

Classic Redux requires a lot of code. **Redux Toolkit** (RTK) simplifies everything:

```bash
npm install @reduxjs/toolkit react-redux
```

## Create a Slice

A **slice** groups state, reducers and actions for a feature:

```javascript
// features/counter/counterSlice.js
import { createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
    name: 'counter',
    initialState: {
        value: 0
    },
    reducers: {
        increment: (state) => {
            // RTK uses Immer: you can "mutate" directly
            state.value += 1;
        },
        decrement: (state) => {
            state.value -= 1;
        },
        incrementByAmount: (state, action) => {
            state.value += action.payload;
        }
    }
});

// Export actions
export const { increment, decrement, incrementByAmount } = counterSlice.actions;

// Export reducer
export default counterSlice.reducer;
```

## Configure the Store

```javascript
// app/store.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from '../features/counter/counterSlice';

export const store = configureStore({
    reducer: {
        counter: counterReducer
    }
});
```

## Connect React to the Store

```javascript
// index.js or App.js
import { Provider } from 'react-redux';
import { store } from './app/store';

function App() {
    return (
        <Provider store={store}>
            <Counter />
        </Provider>
    );
}
```

## Use Redux in a Component

```javascript
// features/counter/Counter.js
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement, incrementByAmount } from './counterSlice';

function Counter() {
    // Read state
    const count = useSelector(state => state.counter.value);

    // Get dispatch function
    const dispatch = useDispatch();

    return (
        <div>
            <p>{count}</p>
            <button onClick={() => dispatch(increment())}>+1</button>
            <button onClick={() => dispatch(decrement())}>-1</button>
            <button onClick={() => dispatch(incrementByAmount(5))}>+5</button>
        </div>
    );
}
```

## Complete Example: Shopping Cart

### The Slice

```javascript
// features/cart/cartSlice.js
import { createSlice } from '@reduxjs/toolkit';

const cartSlice = createSlice({
    name: 'cart',
    initialState: {
        items: [],
        total: 0
    },
    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 });
            }

            state.total = state.items.reduce(
                (sum, item) => sum + item.price * item.quantity,
                0
            );
        },

        removeItem: (state, action) => {
            state.items = state.items.filter(item => item.id !== action.payload);
            state.total = state.items.reduce(
                (sum, item) => sum + item.price * item.quantity,
                0
            );
        },

        updateQuantity: (state, action) => {
            const { id, quantity } = action.payload;
            const item = state.items.find(item => item.id === id);

            if (item) {
                item.quantity = quantity;
                if (item.quantity <= 0) {
                    state.items = state.items.filter(i => i.id !== id);
                }
            }

            state.total = state.items.reduce(
                (sum, item) => sum + item.price * item.quantity,
                0
            );
        },

        clearCart: (state) => {
            state.items = [];
            state.total = 0;
        }
    }
});

export const { addItem, removeItem, updateQuantity, clearCart } = cartSlice.actions;
export default cartSlice.reducer;
```

### The Components

```javascript
// components/ProductCard.js
import { useDispatch } from 'react-redux';
import { addItem } from '../features/cart/cartSlice';

function ProductCard({ product }) {
    const dispatch = useDispatch();

    const handleAddToCart = () => {
        dispatch(addItem(product));
    };

    return (
        <div className="product-card">
            <h3>{product.name}</h3>
            <p>{product.price} €</p>
            <button onClick={handleAddToCart}>Add to cart</button>
        </div>
    );
}
```

```javascript
// components/Cart.js
import { useSelector, useDispatch } from 'react-redux';
import { removeItem, updateQuantity, clearCart } from '../features/cart/cartSlice';

function Cart() {
    const { items, total } = useSelector(state => state.cart);
    const dispatch = useDispatch();

    if (items.length === 0) {
        return <p>Empty cart</p>;
    }

    return (
        <div className="cart">
            <h2>Cart</h2>
            {items.map(item => (
                <div key={item.id} className="cart-item">
                    <span>{item.name}</span>
                    <input
                        type="number"
                        value={item.quantity}
                        onChange={(e) => dispatch(updateQuantity({
                            id: item.id,
                            quantity: parseInt(e.target.value)
                        }))}
                        min="0"
                    />
                    <span>{item.price * item.quantity} €</span>
                    <button onClick={() => dispatch(removeItem(item.id))}>
                        Remove
                    </button>
                </div>
            ))}
            <p><strong>Total: {total} €</strong></p>
            <button onClick={() => dispatch(clearCart())}>Clear cart</button>
        </div>
    );
}
```

## Async Actions with createAsyncThunk

For API calls:

```javascript
// features/users/usersSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';

// Async action
export const fetchUsers = createAsyncThunk(
    'users/fetchUsers',
    async () => {
        const response = await fetch('/api/users');
        return response.json();
    }
);

const usersSlice = createSlice({
    name: 'users',
    initialState: {
        items: [],
        loading: false,
        error: null
    },
    reducers: {},
    extraReducers: (builder) => {
        builder
            .addCase(fetchUsers.pending, (state) => {
                state.loading = true;
                state.error = null;
            })
            .addCase(fetchUsers.fulfilled, (state, action) => {
                state.loading = false;
                state.items = action.payload;
            })
            .addCase(fetchUsers.rejected, (state, action) => {
                state.loading = false;
                state.error = action.error.message;
            });
    }
});

export default usersSlice.reducer;
```

```javascript
// Usage
function UserList() {
    const { items, loading, error } = useSelector(state => state.users);
    const dispatch = useDispatch();

    useEffect(() => {
        dispatch(fetchUsers());
    }, [dispatch]);

    if (loading) return <p>Loading...</p>;
    if (error) return <p>Error: {error}</p>;

    return (
        <ul>
            {items.map(user => (
                <li key={user.id}>{user.name}</li>
            ))}
        </ul>
    );
}
```

## Selectors

**Selectors** are functions to extract data from the store:

```javascript
// features/cart/cartSelectors.js

// Simple selector
export const selectCartItems = state => state.cart.items;
export const selectCartTotal = state => state.cart.total;

// Computed selector
export const selectCartItemCount = state =>
    state.cart.items.reduce((count, item) => count + item.quantity, 0);

// Usage
function CartIcon() {
    const itemCount = useSelector(selectCartItemCount);
    return <span className="cart-icon">🛒 {itemCount}</span>;
}
```

## When to Use Redux vs Context?

| Criteria | Context | Redux |
| --- | --- | --- |
| Simple global data (theme, user) | ✅ | ❌ Overkill |
| Shared state between distant components | ✅ | ✅ |
| Complex update logic | ❌ | ✅ |
| Async actions | Manual | ✅ createAsyncThunk |
| DevTools (time-travel debug) | ❌ | ✅ |
| Large application | ❌ Performance | ✅ |

## Recommended Structure

```
src/
  app/
    store.js
  features/
    cart/
      cartSlice.js
      cartSelectors.js
      Cart.js
      CartIcon.js
    users/
      usersSlice.js
      UserList.js
    products/
      productsSlice.js
      ProductList.js
```

## Key Points to Remember

1. **Single store**: one single source of truth
2. **Actions**: describe what happened
3. **Reducers**: calculate the new state
4. **Redux Toolkit**: simplifies Redux with createSlice
5. **useSelector**: read state
6. **useDispatch**: send actions
7. **createAsyncThunk**: async actions

[<- Previous Chapter](/training/module4-react-avance/custom-hooks) | [Back to module](/training/module4-react-avance) | [Next Module: SmartMaker Architecture ->](/training/module5-architecture-smartmaker)
