A simple example of a Cart to remind me the basic usage of Redux Toolkit. We need to use two features : "products" and "cart". The Products React Component displays a list of products and add it to the cart. The Cart Component displays the number of items and the total score.
File structure :
- ./main.jsx
- ./App.jsx
- ./app/store.js
- ./features/products/Products.jsx
- ./features/products/productsSlice.jsx
- ./features/cart/Cart.jsx
- ./features/cart/cartSlice.jsx
Thanks to : Modern Redux with Redux Toolkit (RTK) and TypeScript on Egghead.io
As usual, encapsulate the App with the Provider.
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import App from './App';
import { store } from './app/store';
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);Useless App file just to import components.
import Products from './features/products/Products';
import Cart from './features/cart/Cart';
function App() { return <> <Products /><Counter /></> }
export default App;Combine all reducers in a single file.
import { configureStore } from '@reduxjs/toolkit';
import productsReducer from '../features/products/productsSlice';
import cartReducer from '../features/cart/cartSlice';
export const store = configureStore({
reducer: {
cart: cartReducer,
products: productsReducer,
},
});import { createSlice } from '@reduxjs/toolkit';
const productsSlice = createSlice({
name: 'products',
initialState: { products: {} },
reducers: {
loadProducts(state, action) {
const products = action.payload;
products.forEach((product) => {
state.products[product.id] = product;
});
},
},
});
export const { loadProducts } = productsSlice.actions;
export default productsSlice.reducer;import { createSlice, createSelector } from '@reduxjs/toolkit';
const cartSlice = createSlice({
name: 'cart',
initialState: { items: {} },
reducers: {
addToCart(state, action) {
const id = action.payload;
if (state.items[id]) state.items[id]++;
else state.items[id] = 1;
},
},
});
export const { addToCart } = cartSlice.actions;
export default cartSlice.reducer;
//Selectors
export const getNumItems = (state) => {
let num = 0;
for (let id in state.cart.items) {
num += state.cart.items[id];
}
return num;
};
export const getTotalPrice = createSelector(
(state) => state.products.products,
(state) => state.cart.items,
(products, items) => {
let total = 0;
for (let id in items) {
total += products[id].price * items[id];
}
return total.toFixed(2);
}
);import { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { loadProducts } from './productsSlice';
import { addToCart } from '../cart/cartSlice';
const DATA = [
{ name: 'Lamp', price: 10, id: 1 },
{ name: 'Usb Key', price: 15, id: 2 },
{ name: 'Apple', price: 1, id: 3 },
{ name: 'Book', price: 12, id: 4 },
{ name: 'Rice', price: 2.5, id: 5 },
];
const Products = () => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(loadProducts(DATA));
}, []);
const products = useSelector((state) => state.products.products);
return (
<section>
<h2>Products</h2>
{Object.values(products).map((item) => (
<div key={item.id}>
{item.name} : {item.price}€
<button onClick={() => dispatch(addToCart(item.id))}>Add</button>
</div>
))}
</section>
);
};
export default Products;import { useSelector, useDispatch } from 'react-redux';
import { getNumItems, getTotalPrice } from './cartSlice';
const Cart = () => {
const products = useSelector((state) => state.products.products);
const items = useSelector((state) => state.cart.items);
const nbItems = useSelector(getNumItems);
const totalPrice = useSelector(getTotalPrice);
return (
<div>
<h2>Cart : {nbItems}</h2>
<hr />
<ul>
{Object.entries(items).map(([id, quantity]) => (
<li key={products[id].id}>
{products[id].name}- {quantity}
</li>
))}
</ul>
<p>TOTAL : {totalPrice}€ </p>
</div>
);
};
export default Cart;