This is an e-commerce store built with the FakeStore API using React, React Query, TailWind CSS, Vite and Netlify for CI/CD.

Challenge 1
Every time a user added an item to their cart, it triggered re-renders all the way up the component tree, including components that didn’t need to update. This led to performance issues and inefficient rendering across the app.

Approach
To solve this, I modularized the cart functionality by:
Moving all cart logic (state + actions) into a custom hook:
This included cart, addToCart, removeFromCart, totalItems, and totalPrice. The hook handles localStorage persistence and ensures cart logic is completely isolated from the App
component.
Memoize return object
useMemo now stabilizes the return object
CartProvider’s context value doesn’t change unless something actually changed
addToCart function no longer changes reference
import { useState, useEffect, useMemo, useCallback } from 'react';
const CART_KEY = 'user-cart';
// Initialize cart state from local storage for persistence across page reloads
export default function useCart(){
const [cart, setCart] = useState(() => {
const saved = localStorage.getItem(CART_KEY);
try {
return saved ? JSON.parse(saved) : [];
} catch {
localStorage.removeItem(CART_KEY);
return [];
}
});
// Sync cart state to localStorage whenever it changes
useEffect(() => {
localStorage.setItem(CART_KEY, JSON.stringify(cart));
}, [cart]);
// Add product to cart or increase quantity if it already exists
const addToCart = useCallback((product) => {
setCart((prevCart) => {
const updatedCart = prevCart.reduce((acc, item) => {
if (item.id === product.id) {
acc.push({ ...item, quantity: item.quantity + 1 });
} else {
acc.push(item);
}
return acc;
}, []);
...
// MEMOIZE THE RETURN OBJECT
return useMemo(() => ({
cart,
addToCart,
removeFromCart,
totalItems,
totalPrice,
}), [cart, addToCart, removeFromCart, totalItems, totalPrice]);
Separating Cart Context into two parts:
CartActionsContext: holds non-changing functions like addToCart and removeFromCart
CartStateContext: holds reactive values like cart, totalItems, and totalPrice
import { createContext, useContext, useMemo } from 'react';
import useCart from '../hooks/useCart';
const CartStateContext = createContext();
const CartActionsContext = createContext();
export function CartProvider({ children }) {
const { cart, totalItems, totalPrice, addToCart, removeFromCart } = useCart();
const stateValue = useMemo(() => ({
cart,
totalItems,
totalPrice
}), [cart, totalItems, totalPrice]);
const actionsValue = useMemo(() => ({
addToCart,
removeFromCart
}), [addToCart, removeFromCart]);
return (
<CartStateContext.Provider value={stateValue}>
<CartActionsContext.Provider value={actionsValue}>
{children}
</CartActionsContext.Provider>
</CartStateContext.Provider>
);
}
export const useCartState = () => useContext(CartStateContext);
export const useCartActions = () => useContext(CartActionsContext);
Using the context selectively at the component level:
Instead of passing cart-related props through multiple layers, components now consume only the data or actions they need, directly from context.
import { useCartActions, useCartState } from "../context/CartContext";
const SideCart = () => {
const { addToCart, removeFromCart } = useCartActions();
const { cart, totalPrice, totalItems } = useCartState();
...
Result
By:
- Extracting cart logic into a custom hook
- Splitting state and actions into separate contexts
- Consuming them only where needed
I was able to significantly reduce unnecessary re-renders across the app. Components like SingleProduct, Hero, and ProductRow now stay completely untouched when the cart updates, while components like SideCart and Navbar re-render appropriately.
This approach not only improved performance, but also made the cart functionality more modular, testable, and easier to maintain.

Challenge 2
To improve optimization and performance, I leveraged the useQuery
hook to cache all products retrieved from the API. This approach worked well since the dataset was static and rarely changed.
Approach
To make the solution scalable, I integrated React Query (TanStack Query) with a stale and cache time set to one week. This ensures efficient data retrieval by storing the results locally, minimizing unnecessary API calls.
const { data: products, isLoading: loading, error } = useQuery({
queryKey: ['products'],
queryFn: fetchProducts,
staleTime: 1000 * 60 * 60 * 24 * 7, // 1 week
cacheTime: 1000 * 60 * 60 * 24 * 7, // Cached for a week even if no components are using
});
Result
This approach significantly reduced unnecessary API requests, improved load times, and provided a smoother user experience. By caching the product data for one week, the app delivered faster performance while maintaining up to date content with minimal overhead.