useState rerenders bindings increment when key added to object but not removed

I’m running a bunch of generated checkboxes through useState as a generic object, eg:

const [stuff, setStuff] = useState(() => {
  // do stuff to get saved stuff.
  // assume very basic format of { uuid: boolean, uuid: boolean, ... }
  return theStuff
}

The stuff is then fed to the UI by child components that uses the stuff, eg:

{stuff.map((thing, i) => (
  <Fragment key={i}>
    <input
      type="checkbox"
      id={thing}
      name={thing}
      checked={stuff[thing] || false}
      onChange={() => handleChanges(thing)}
    />
  </Fragment>
)};

Now when one of the few hundred checkboxes is created and one/many get checked I just throw the state object mentioned in the first snippet. This works great, updates the state on the screen re-renders and also updates the values and re-renders on other sibling components so I can display like counts of selected etc.

export const handleChanges = (thing) => {
  setStuff((prevStuff) => ({
    ...prevStuff,
    [thing]: !prevStuff[thing]
  }));
}

The Problem
It may be in handleChanges I assume my learning curve on React lifecycle handling is hurting me because:

  • It will update the used values in sibling components when I ADD to the object, but not when I REMOVE from the object.
  • If I handle the change by way of a delete stuff.uuid then it WILL update the values used on the sibling components both ways…but it does not update the checked state of the checkbox.

Hope this isn’t too verbose, but I can’t seem to find how to have setState update both when adding and removing from the object on the sibling components. This while keeping the state object a nice clean object of only the uuid’s selected.