Product list with cart using React

Solution retrospective
I am proud of learning how to create & use React Context & Provider:
interface CartContextType {
products: Product[];
cart: CartItemType[];
addToCart: (product: Product) => void;
removeFromCart: (productId: number) => void;
emptyCart: () => void;
findCartItem: (productId: number) => CartItemType | undefined;
reduceQty: (product: Product) => void;
increaseQty: (product: Product) => void;
getTotalCost: () => number;
isModalOpen: boolean;
openModal: () => void;
closeModal: () => void;
resetCart: () => void;
}
export const CartContext = createContext<CartContextType | undefined>(
undefined,
);
export const CartProvider: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
const { products } = useFetchProducts();
const [cart, setCart] = useState<CartItemType[]>([]);
const [isModalOpen, setIsModalOpen] = useState(false);
const openModal = useCallback(() => setIsModalOpen(true), []);
const closeModal = useCallback(() => setIsModalOpen(false), []);
const resetCart = useCallback(() => {
setCart([]);
closeModal();
}, [closeModal]);
const addToCart = useCallback((product: Product) => {
setCart((prevCart) => {
return [...prevCart, { ...product, quantity: 1 }];
});
}, []);
const increaseQty = useCallback((product: Product) => {
setCart((prevCart) => {
return prevCart.map((item) =>
item.id === product.id
? { ...item, quantity: item.quantity + 1 }
: item,
);
});
}, []);
const reduceQty = useCallback((product: Product) => {
setCart((prevCart) => {
const updatedCart = prevCart
.map((item) => {
if (item.id === product.id) {
const qty = item.quantity;
if (qty === 1) {
return null;
}
return { ...item, quantity: item.quantity - 1 };
}
return item;
})
.filter((item): item is CartItemType => item !== null);
return updatedCart;
});
}, []);
const removeFromCart = useCallback((productId: number) => {
setCart((prevCart) => prevCart.filter((item) => item.id !== productId));
}, []);
const emptyCart = useCallback(() => {
setCart([]);
}, []);
const findCartItem = useCallback(
(productId: number) => {
return cart.find((item) => item.id === productId);
},
[cart],
);
const getTotalCost = useCallback(() => {
return cart.reduce((acc, item) => acc + item.price * item.quantity, 0);
}, [cart]);
const contextValue = useMemo(
() => ({
products,
cart,
addToCart,
removeFromCart,
emptyCart,
findCartItem,
reduceQty,
increaseQty,
getTotalCost,
isModalOpen,
openModal,
closeModal,
resetCart,
}),
[
products,
cart,
addToCart,
removeFromCart,
emptyCart,
findCartItem,
reduceQty,
increaseQty,
getTotalCost,
isModalOpen,
openModal,
closeModal,
resetCart,
],
);
return (
<CartContext.Provider value={contextValue}>{children}</CartContext.Provider>
);
};
export const useCart = () => {
const context = useContext(CartContext);
if (!context) {
throw new Error("useCart must be used within a CartProvider");
}
return context;
};
function App() {
return (
<ThemeProvider theme={theme}>
<CartProvider>
<GlobalStyles />
<Container>
<ProductList />
<Cart />
</Container>
<OrderDialog />
</CartProvider>
</ThemeProvider>
);
}
What specific areas of your project would you like help with?
I still struggle with positioning certain UI elements. Like The "Add to Cart Button" in this challenge. Any tips or resources for learning that would be great!
Please log in to post a comment
Log in with GitHubCommunity feedback
- @PaiKai-Lee
Your project structure and design are excellent — I’ve learned a lot from your work.
- You made great use of useCallback and useMemo in the context to optimize re-renders and avoid unnecessary computations.
- Separating the hooks and provider made the structure much cleaner and easier to maintain.
Join our Discord community
Join thousands of Frontend Mentor community members taking the challenges, sharing resources, helping each other, and chatting about all things front-end!
Join our Discord