The checkout page is the finish line of any e-commerce experience. It requires a delicate balance of handling passed data, calculating totals in real-time, and managing API interactions.
In this article, we’ll explore how to build a Checkout component that processes a shopping basket and handles order placement with grace.
- Accessing the Basket via Router State
A common pattern in React is passing data between routes without using a global store like Redux for everything. Here, we use useLocation to grab the basket passed from the shopping cart.
JavaScript
import { useState } from 'react'
import { useLocation, useNavigate } from 'react-router-dom'
import './Checkout.css'
function Checkout({ user }) {
const location = useLocation()
const navigate = useNavigate()
const basket = location.state?.basket || []
const [success, setSuccess] = useState(false)
const [error, setError] = useState('')
- Calculating the Total
Before the user clicks "Buy," they need to know the damage. We use the .reduce() method to iterate through the basket and calculate a final total based on price and quantity.
JavaScript
const total = basket.reduce((sum, item) => {
return sum + item.product_price * item.quantity
}, 0)
- The placeOrder Logic
The core functionality of this page is the POST request. We map the basket items into a simplified format (ID and quantity) that the backend expects, then handle the response.
JavaScript
const placeOrder = async () => {
if (!user) { navigate('/login'); return }
const res = await fetch('/api/orders', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
customerID: user.customerID,
items: basket.map(item => ({
productID: item.productID,
quantity: item.quantity
}))
})
})
const data = await res.json()
if (!res.ok) { setError(data.error); return }
setSuccess(true)
}
- Conditional UI: Success vs. Checkout
Instead of navigating to a brand new page, we use conditional rendering to swap the UI once the success state is true. This provides a fast, snappy experience for the customer.
JavaScript
if (success) {
return (
Order Placed!
Thank you for your order.
navigate('/orders')}>
View My Orders
)
}
- Building the Review Section
The main render provides a summary of the items and the total. Using .toFixed(2) ensures that our currency is always displayed with two decimal places, avoiding the "ugly" math results often found in JavaScript.
JavaScript
return (
Checkout
{basket.length === 0 ? (
Your basket is empty.
) : (
<>
{basket.map((item, index) => (
{item.product_name}
Quantity: {item.quantity}
£{(item.product_price * item.quantity).toFixed(2)}
))}
Total: £{total.toFixed(2)}
{error &&
{error}
}Place Order
</>
)}
)
}
- Visual Polish and Feedback
The CSS focuses on making the "Order" action prominent while providing clear error feedback if the API call fails.
CSS
/* Styling the success/action button /
.order-btn {
background: #A2C24A;
color: white;
border-radius: 6px;
padding: 12px 30px;
cursor: pointer;
font-family: 'Gluten', cursive; / Adding a bit of brand personality */
}
/* Highlighting the Total /
.checkout-total h2 {
color: #A2C24A; / Matching the button for visual harmony */
}
/* Clear Error Messaging */
.error {
color: white;
background: red;
padding: 10px;
border-radius: 6px;
}
Key Takeaways
Router State: Using location.state is a great way to pass "transient" data like a shopping basket between pages.
Safety First: Always check for a user object before allowing an order submission.
The Power of Reduce: reduce() is the cleanest way to handle mathematical sums in a React component.
Feedback Loops: Use clear success and error states to keep the user informed about what’s happening behind the scenes.
Top comments (0)