Hello! Welcome back to our e-commerce project.
In our last session, we brought the shopping cart to life by adding controls to update item quantities. You mastered the core useReducer pattern: dispatching an action from the UI and handling the state update immutably within the reducer.
Today, we'll complete the core functionality of our shopping cart by tackling the final operation: implementing the functionality to remove an item from the cart. This lesson will reinforce the reducer pattern and introduce another powerful and highly useful array method, .filter(), which directly aligns with your goal of mastering modern JavaScript.
1. The "Remove" Action and the filter Method
The pattern for removing an item is identical to what we used for updating quantities:
- A user clicks a "Remove" button in the UI.
- The
onClickhandler dispatches an action, like{ type: 'REMOVE_ITEM', payload: { id: 123 } }. - Our
cartReducerreceives this action. - The reducer's job is to return a new state object where the
itemsarray no longer contains the item with the specified ID.
To accomplish this immutably, the Array.prototype.filter() method is the perfect tool. It iterates over an array and returns a new array containing only the elements for which your provided callback function returns true. It doesn't change the original array, which is exactly what we need for our reducer.
Let's start by adding the logic to our reducer.
Step 1: Update the cartReducer
Open your src/context/CartContext.jsx file. We will add a new case to our switch statement for REMOVE_ITEM.
The logic is straightforward: we'll use state.items.filter() and keep every item except the one whose ID matches the ID from our action's payload.
// src/context/CartContext.jsx
// ... (your existing imports, initial state, and other reducer cases)
const cartReducer = (state, action) => {
switch (action.type) {
// ... (cases for ADD_ITEM, INCREASE_QUANTITY, DECREASE_QUANTITY)
// --- NEW LOGIC STARTS HERE ---
case 'REMOVE_ITEM':
return {
...state,
// .filter() creates a new array with all items that pass the test.
// We keep every item where the ID does NOT match the one we want to remove.
items: state.items.filter(item => item.id !== action.payload.id),
};
// --- NEW LOGIC ENDS HERE ---
default:
return state;
}
};
// ... (your CartProvider and useCart hook)
With just that one line of code in our new case, the reducer is now capable of handling item removals. The beauty of .filter() is that it elegantly handles the task while respecting the rule of immutability.
2. Adding the "Remove" Button to the UI
Now that the reducer is ready, we need to provide a way for the user to trigger the REMOVE_ITEM action. We'll add a simple "Remove" button to each item in CartPage.jsx.
Open src/pages/CartPage.jsx. Inside your items.map() loop, add a button that dispatches our new action on click.
// src/pages/CartPage.jsx
// ... (inside the return statement of CartPage.jsx, within the items.map(...) callback)
<div key={item.id} className="flex items-center justify-between border p-4 rounded-lg shadow-sm bg-white">
<div className="flex items-center">
<img src={item.image} alt={item.title} className="w-20 h-20 object-contain mr-4" />
<div className="flex-grow">
<h2 className="text-lg font-semibold">{item.title}</h2>
<p className="text-gray-600">${item.price.toFixed(2)}</p>
</div>
</div>
<div className="flex items-center gap-4">
{/* Quantity Controls from previous lesson */}
<button
onClick={() => dispatch({ type: 'DECREASE_QUANTITY', payload: { id: item.id } })}
className="px-3 py-1 border rounded-md hover:bg-gray-100"
>
-
</button>
<span className="font-semibold">{item.quantity}</span>
<button
onClick={() => dispatch({ type: 'INCREASE_QUANTITY', payload: { id: item.id } })}
className="px-3 py-1 border rounded-md hover:bg-gray-100"
>
+
</button>
</div>
<div className="flex flex-col items-end">
<p className="font-bold text-lg">${(item.price * item.quantity).toFixed(2)}</p>
{/* --- NEW BUTTON STARTS HERE --- */}
<button
onClick={() => dispatch({ type: 'REMOVE_ITEM', payload: { id: item.id } })}
className="text-red-500 hover:text-red-700 text-sm mt-2"
>
Remove
</button>
{/* --- NEW BUTTON ENDS HERE --- */}
</div>
</div>
Note that I've slightly restructured the JSX to better accommodate the new button and align the price and remove button to the right. The key part is the new button and its onClick handler, which dispatches the REMOVE_ITEM action along with the id of the item to be removed.
That's it! When you click the "Remove" button, the action is dispatched, the reducer filters the item out, a new state is generated, and the CartPage re-renders to show that the item is gone. The total price and item count will also update automatically.
3. Visualizing the Implementation
To see this exact process put together, let's watch a clip that demonstrates implementing both the UI button and the reducer logic.
Build a Shopping Cart with React JS, Context API, and useReducer Hook | Ecommerce Website Tutorial
The following segment from the "Build a Shopping Cart" tutorial by Code With Yousaf shows the implementation of the remove functionality we just built. It will help you visualize how the button's dispatch call connects to the .filter() logic in the reducer.
Watch the clip from 00:51:10 to 00:53:06. Notice how an onClick handler is added to the remove button to dispatch an action with { type: 'remove', ... }. Then, pay close attention to how the remove case in the reducer uses state.filter() to create the new array of items.
This video provides a great real-world confirmation of the pattern we've implemented.
Conclusion
Congratulations! You have now built a fully functional shopping cart state management system. By adding the remove functionality, you've completed the core "CRUD" (Create, Read, Update, Delete) operations for the items in your cart:
- Create:
ADD_ITEM - Read: Displaying items on the
CartPage - Update:
INCREASE_QUANTITY&DECREASE_QUANTITY - Delete:
REMOVE_ITEM
Key Takeaways:
- The reducer pattern is consistent and powerful for all types of state mutations.
Array.prototype.filter()is the ideal immutable method for removing elements from an array based on a condition.- By centralizing logic in the
cartReducer, our components remain clean and only need to worry about dispatching actions.
Preview of the Next Lesson:
Our React application has been working in isolation with mock data. In the next module, we will begin the exciting process of connecting it to your Laravel backend. The very first challenge we need to solve is enabling communication between the two different domains (your React app's server and your Laravel API's server). We will start by learning how to configure your Laravel backend to handle Cross-Origin Resource Sharing (CORS), a fundamental step in building any full-stack application.