Reputation: 29
I had some problems while implementing React Context in my NextJS ecommerce project. When adding products to the cart, everything was fine but when I opened the Cart modal to increase or decrease the quantity of each product before confirming the order, the component didn't re-render. It only re-rendered after I closed the modal. Here is the link to my project on Code sandbox: https://codesandbox.io/s/hopeful-hill-z31o49
Here is the quick demo video of the problem: https://youtu.be/tRD7Hs-9rOw
This is the context file
export const CartContext = createContext<ContextProps>(null);
export const ContextProvider = function ({ children }) {
const [openModal, setOpenModal] = useState(false);
const [cart, setCart] = useState<any[]>([]);
const ctx = {
openModal,
setOpenModal,
cart,
setCart,
};
return <CartContext.Provider value={ctx}>{children}</CartContext.Provider>;
};
The component that had problems while using Context
// ./components/Modal.tsx
const Modal = () => {
const { setOpenModal, cart, setCart } = useContext(CartContext);
console.log("🚀 -> cart", cart);
const totalPrice = cart.reduce(
(sum, currProduct) => sum + currProduct.price * currProduct.quantity,
0,
);
function increaseQuantity(product: any) {
let updatedCart = cart;
const idx = updatedCart.findIndex((p) => p.name === product.name);
updatedCart[idx].quantity += 1;
setCart(updatedCart);
console.log("🚀 -> updatedCart", updatedCart);
}
function decreaseQuantity(product: any) {
let updatedCart = cart;
if (product.quantity > 1) {
const idx = updatedCart.findIndex((p) => p.name === product.name);
updatedCart[idx].quantity -= 1;
setCart(updatedCart);
console.log("🚀 -> updatedCart", updatedCart);
} else {
updatedCart = updatedCart.filter((p) => p.name !== product.name);
}
setCart(updatedCart);
}
return (
<div>
...
{cart.map((product) => (
<div>
<button onClick={() => increaseQuantity(product)}></button>
<div>{product.quantity}</div>
<button onClick={() => decreaseQuantity(product)}></button>
</div>
))}
...
</div>
)
Upvotes: 3
Views: 1320
Reputation: 2157
The reason of useConext
didn't trigger the render is because React doesn't do deep equal comparison on state object. When you call setCart(updatedCart);
, React doesn't check all the items in the cart to look for difference. It only checks if the instance of the cart has changed. So in this case, it will treat it as no chance because you only update the item in the cart but not the instance of the cart. To correct this, you can replace the line
setCart(updatedCart);
to
setCart([...updatedCart]);
This create a copy of the cart instead of reusing the same cart, so it would trigger React to render the update.
However, sometimes you don't want to do this especially when creating a copy of the array is too expensive. An easy trick to trigger an update instead of creating a copy of the state is to add a state to trigger React to render
on update.
In /components/Modal.tsx
You can add a state and update it whenever you make a update on the UI, like below
const Modal = () => {
const { setOpenModal, cart, setCart } = useContext(CartContext);
const { lastUpdate, setLastUpdate } = useState(0);
...
function increaseQuantity(product: any) {
...
setLastUpdate(Date.now());
...
}
function decreaseQuantity(product: any) {
...
setLastUpdate(Date.now());
...
}
Upvotes: 4