State is defined like so:
const [items, setItems] = useState([] as CartItemType[]); const [id, setId] = useState<number | undefined>();
In this case, id
is totally useless. Don’t need it in my app at all.
However, if I try to update items
, the state variable doesn’t change and the UI doesn’t reload, unless I also update id
:
useEffect(() => console.log("reload")); // only fires if I include setId const clickItem = (item: CartItemType) => { let tempItems = data; // @ts-ignore tempItems[item.id - 1].animation = "item animate__animated animate__zoomOut"; setItems(tempItems!); // "!" to get rid of ts complaint about possible undefined value setId(item.id); // nothing happens if I don't include this }; // ... inside the return, in a map fn <Item item={item} handleAddToCart={handleAddToCart} clickItem={clickItem} /> // inside Item component <StyledItemWrapper className={item.animation} onClick={() => { clickItem(item); // item = an obj containing an id and an animation property }} >
Why is setId
necessary here? What is it doing that setItems
isn’t?
Advertisement
Answer
The reason is because setState
uses Object.is
equality by default to compare the old and the new values and tempItems === items
even after you mutate one of the objects inside of it.
If you update a State Hook to the same value as the current state, React will bail out without rendering the children or firing effects.
You can solve this by only mutating a copy of the array:
let tempItems = [...data]; // You call it `data` here, but I assume it's the same as `items` above.
but you’ll run into the same problem if anything depends on item
changing, so then you have to copy everything, which is more expensive:
let tempItems = data.map(d => ({...d}));
The alternative is to only copy what you’re going to mutate (or switch to an immutable datastructure library like Immer or Immutable.js):
let lastIndex = data.length - 1; // Copy _only_ the value we're going to mutate let tempItems = data.map((d, i) => i !== lastIndex ? d : {...d});