Skip to content
Advertisement

React State only updates when setting a useless state variable along with the necessary state variable

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});
User contributions licensed under: CC BY-SA
9 People found this is helpful
Advertisement