I'm trying to create a cart mechanism using react context.
This is my context provider
export default function App() {
const [cart, setCart] = useState(cartInitialState);
return (
<CartContext.Provider value={{ cart, setCart }}>
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
<MenuItemDetailsView />
</CartContext.Provider>
);
}
Where cart initial state is some dummy empty cart. The MenuItemDetailsView is a child view that uses the parent component context
function MenuItemDetailsView() {
const { cart, setCart } = useContext(CartContext);
const findItemInCart = () => {
//return quantity of cart item with specific id
};
const handleAdd = () => {
const itemIndex = cart.items.findIndex((item) => item.id === 0);
let newCart = cart;
console.debug(newCart);
if (itemIndex === -1) {
//do stuff
} else {
//do stuff
}
setCart(newCart);
};
return (
<div>
<h1>{findItemInCart()}</h1>
<button onClick={handleAdd}>add</button>
</div>
);
}
The child view should be able to update the context, which it does correctly. The problem is that the parent App is not rerendering the MenuItemDetailsView component after updating the context. (The App component rerender itself
Here is a sandbox of my code
https://codesandbox.io/s/react-context-demo-cart-eyjx2
I'm not quite understanding the problem here. Shouldn't the App component rerender its children too? If not, how can I tell him to rerender when updating the context?
I understand the possibility to set a local child variable and update both the variable and the context itself when updating the quantities but I feel that would be a workaround to the real problem.
SN: When exiting the MenuItemView and reentering it (from a multipage app, not displayed on sandbox) the quantity gets updated, that means that useContext is correctly working and that the cart item is being correctly read every time the MenuItemView is being rerendered. But again I want that the cart gets updated without getting out of the component and getting back in manually
CodePudding user response:
The issue here is that you're mutating the cart state.
let newCart = cart;
The newCart
is still referencing the same object that you keep in state, so when you update newCart
, you are mutating cart
as well. Once you do setCart(newCart)
, React bails out of the state update (and rerender) because it's the same object as before. There is more info on how React compares objects when updating state in the React docs.
The solution here is to copy the state first as a new object:
const newCart = { ...cart };
or better yet, use the functional state update:
setCart(prevCart => {
const newCart = { ...prevCart };
// do stuff with newCart
return newCart;
});