React Redux con Redux Toolkit: un carrello e-commerce

In questo articolo costruiremo un carrello e-commerce con React Redux e Redux Toolkit, seguendo le pratiche consigliate.

Perché usare Redux Toolkit con React Redux

  • API moderne e concise (createSlice, configureStore) riducono il boilerplate.
  • Performance e DX: integrazione con DevTools, middleware preconfigurati e tipizzazione facilitata.
  • Compatibilità aggiornata: React Redux è allineato a React e a build ESM/CJS moderne.

Installazione

npm install @reduxjs/toolkit react-redux

Struttura minima

  • src/app/store.js — configurazione dello store
  • src/features/cart/cartSlice.js — stato e reducer del carrello
  • src/App.jsx — UI (lista prodotti + carrello)
  • src/main.jsx — bootstrap React con <Provider>

Slice del carrello

// src/features/cart/cartSlice.js
import { createSlice, createSelector } from '@reduxjs/toolkit';

const initialState = {
// mappa id => { id, name, price, qty }
  items: {}
};

const cartSlice = createSlice({
name: 'cart',
initialState,
reducers: {
  addedToCart(state, action) {
    const p = action.payload; // {id, name, price}
    const curr = state.items[p.id];
    state.items[p.id] = curr
    ? { ...curr, qty: curr.qty + 1 }
    : { ...p, qty: 1 };
  },
  removedFromCart(state, action) {
    delete state.items[action.payload]; // payload = productId
  },
  changedQty(state, action) {
    const { id, qty } = action.payload;
    if (state.items[id]) {
      state.items[id].qty = Math.max(1, qty);
    }
  },
  clearedCart(state) {
    state.items = {};
  }
}
});

export const { addedToCart, removedFromCart, changedQty, clearedCart } = cartSlice.actions;
export default cartSlice.reducer;

// --- Selectors ---
const selectCartState = (root) => root.cart;

export const selectItemsArray = createSelector(
  selectCartState,
  (cart) => Object.values(cart.items)
);

export const selectCount = createSelector(selectItemsArray, (arr) =>
  arr.reduce((sum, it) => sum + it.qty, 0)
);

export const selectTotal = createSelector(selectItemsArray, (arr) =>
  arr.reduce((sum, it) => sum + it.qty * it.price, 0)
); 

Store Redux

// src/app/store.js
import { configureStore } from '@reduxjs/toolkit';
import cartReducer from '../features/cart/cartSlice';

export const store = configureStore({
  reducer: {
    cart: cartReducer
  }
});

// Tip: in DevTools vedrai le azioni e lo stato senza configurazioni extra 

Bootstrap

// src/main.jsx
import React from 'react';
import { createRoot } from 'react-dom/client';
import { Provider } from 'react-redux';
import { store } from './app/store';
import App from './App';

createRoot(document.getElementById('root')).render(
<React.StrictMode>
  <Provider store={store}>
    <App />
  </Provider>
</React.StrictMode>
); 

UI di esempio: lista prodotti + carrello

// src/App.jsx
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import {
  addedToCart,
  removedFromCart,
  changedQty,
  clearedCart,
  selectItemsArray,
  selectCount,
  selectTotal
} from './features/cart/cartSlice';

const products = [
  { id: 'p1', name: 'T-shirt', price: 19.9 },
  { id: 'p2', name: 'Sneakers', price: 79.0 },
  { id: 'p3', name: 'Zaino', price: 49.5 }
];

export default function App() {
  const dispatch = useDispatch();
  const items = useSelector(selectItemsArray);
  const count = useSelector(selectCount);
  const total = useSelector(selectTotal);

return (
<>
<h1>E-commerce Demo</h1>
  <h2>Prodotti</h2>
  {products.map(p => (
    <figure key={p.id}>
      <figcaption>{p.name} — € {p.price.toFixed(2)}</figcaption>
      <button onClick={() => dispatch(addedToCart(p))}>Aggiungi al carrello</button>
    </figure>
  ))}

  <h2>Carrello ({count})</h2>
  {items.length === 0 ? (
    <p>Il tuo carrello è vuoto.</p>
  ) : (
    <ul>
      {items.map(it => (
        <li key={it.id}>
          <strong>{it.name}</strong> — € {it.price.toFixed(2)}
           × 
          <input
            type="number"
            min="1"
            value={it.qty}
            onChange={(e) =>
              dispatch(changedQty({ id: it.id, qty: Number(e.target.value) }))
            }
            style={{ width: 56 }}
          />
           
          <button onClick={() => dispatch(removedFromCart(it.id))}>Rimuovi</button>
        </li>
      ))}
    </ul>
  )}

  <p><strong>Totale:</strong> € {total.toFixed(2)}</p>
  <button onClick={() => dispatch(clearedCart())}>Svuota carrello</button>
</>

);
} 

Note importanti

  • React 18 obbligatorio per React Redux 9 (niente supporto a React 16/17).
  • Versioni attuali: React Redux 9.2.0 su npm; Redux Toolkit 2.9.0 su GitHub. Controlla sempre i changelog prima di aggiornare.
  • Starter consigliati: template ufficiale Redux+TS per Vite o Next.js con Redux preconfigurato.

Estensioni utili

  • Persistenza: salva lo stato del carrello su localStorage con un middleware custom.
  • RTK Query: gestisci il catalogo prodotti remoto (fetch, cache, polling) separando “server state” dal carrello.

Conclusione

Con React Redux e Redux Toolkit ottieni uno store semplice da configurare, reducer espressivi e selettori performanti. La base mostrata qui è pronta per crescere: aggiungi autenticazione, persistenza, pagamenti e integrazioni API mantenendo uno stato globale prevedibile e testabile.

Torna su