DEV Community

Cover image for React Hooks Snippet: Shopping Cart
Ryan Kahn (he/him)
Ryan Kahn (he/him)

Posted on • Edited on

14 7

React Hooks Snippet: Shopping Cart

Hey all! How would you model a shopping cart with React Hooks? Here's how I would do it!

The main things to look at:

  • This is written in Typescript, to assist the gist also contains the same code in JavaScript.
  • The Types!
    • An order is Tuple of an ID (which is a string) and a Quantity (which is a number).
    • The Cart is a Record, which is an object where the keys are the item IDs and the values are their Quantity.
  • The reducer for useReducer doesn't take a Flux Standard Action! 🤯😱 Let's keep things less complex! Here our reducer is just taking our Order tuples, and reducing the Cart from that.
  • We have two effects we run in our useShoppingCart hook.
    • First, we fetch the saved cart from the server, and add all those items to the cart
    • Second, every time the cart updates we POST that to the server. We keep track of the saved and error status with useState.
    • Could we save the cart before we fetch the cart? I dunno! Maybe?

If shopping carts aren't your thing, but you like this style, leave a comment with what hooks snippet I should write next!

import { useReducer, useState, useEffect } from "react";
type ID = string;
type Quantity = number;
type Order = [ID, Quantity];
type Cart = Record<ID, Quantity>;
const reduceCartFromOrders = (current: Cart, [id, quantity]: Order) => {
if (current === null) current = {};
if (quantity === null) {
delete current[id];
return current;
}
const currentQuantity = current[id] || 0;
const newQuantity = Math.max(currentQuantity + quantity, 0);
return {
...current,
[id]: newQuantity
};
};
export function useShoppingCart(userId: string) {
const [cart, processOrder]: [Cart, (o: Order) => void] = useReducer(
reduceCartFromOrders,
null
);
const addItem = id => processOrder([id, 1]);
const subtractItem = id => processOrder([id, -1]);
const removeItem = id => processOrder([id, null]);
const [error, setError] = useState<string>(null);
const [saved, setSaved] = useState<boolean>(false);
useEffect(() => {
if (userId !== null) {
fetch(`/cart/${userId}`)
.then(resp => resp.json())
.then(
initialCart =>
Object.keys(initialCart).forEach(id =>
processOrder([id, initialCart[id]])
)
)
.catch(error => {
setError(error.toString());
});
}
}, [userId]);
useEffect(() => {
if (userId !== null && cart !== null) {
setSaved(false);
fetch(`/cart/${userId}`, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(cart)
})
.then(() => {
setSaved(true);
setError(null);
})
.catch(error => {
setSaved(false);
setError(error.toString());
});
}
}, [cart, userId]);
return {
cart,
error,
saved,
processOrder,
addItem,
subtractItem,
removeItem
};
}
view raw cart.ts hosted with ❤ by GitHub

Image of Timescale

🚀 pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applications—without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read full post →

Top comments (0)

Postmark Image

Speedy emails, satisfied customers

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up