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 astring
) and aQuantity
(which is anumber
). - The
Cart
is aRecord
, which is an object where the keys are the itemID
s and the values are theirQuantity
.
- An order is Tuple of an
- The reducer for
useReducer
doesn't take a Flux Standard Action! 🤯😱 Let's keep things less complex! Here our reducer is just taking ourOrder
tuples, and reducing theCart
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
anderror
status withuseState
. - 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 | |
}; | |
} |
Top comments (0)