Search code examples
reactjsstatematerial-uireact-hooksstoppropagation

event.stopPropagation seems to prevent useState update and render


I have multiple rows in a list that are Material-UI expansion panels. The rows are mapped from an array of rowData. Each row can expand. Each row also has a delete icon that when push should delete the row.

First Problem is that when I click the DeleteIcon, the row is deleted, but the click propagates and opens the following panel.

<DeleteIcon
  id={key}
  onClick={event => {
     onDelete(event, key); //key is specific row id
  }}
  className={classes.icons}
/>

const onDelete = (event, key) => {
    let newCategories = categories;
    delete newCategories[key];
    setCategories(newCategories);
};

First, I tried to set the panel to false to close it:

const [expanded, setExpanded] = useState(false);
...
const onDelete = (event, key) => {
  setExpanded(false)
...

but that doesn't work because, I assume, my click is being propagated and the panel under the row I deleted is opening from the click event function that runs after onDelete().

So next I tried using event.stopPropagation:

const onDelete = (event, key) => {
    event.stopPropagation();
    let newCategories = categories;
    delete newCategories[key];
    setCategories(newCategories);
  };

But now the row doesn't delete in the view (it does in the console). I assume that event.stopPropagation is preventing my state (setCategories) from forcing a re-render as changes inside the useState hook usually do. However, if I click on the delete icon, then after I click on the row to expanded the panel, the state gets updated and the row deletes. So I have taken out the event.stopPropagation and now I have this:

const onDelete = (event, key) => {
    let newCategories = categories;
    delete newCategories[key];
    setCategories(newCategories);
    setTimeout(() => {
      setExpanded(false);
    }, 100);
};

This works and adds a neat little effect of the following panel very briefly opening before the row gets deleted but I'd like to have a better idea of what is going on with using event.stopPropagation and update state using useState hooks.

EDIT:

Link to codesandbox: https://codesandbox.io/embed/61j19k1q13

I left event.stopPropagation() in the onDelete() function to test. Click on the delete icon and watch nothing happen. Then click on the row to expand the panel and notice it disappears. Thanks!


Solution

  • React is avoiding a re-render because you called the state setter with the exact same object. You should be creating a new object rather than mutating the existing object in order to guarantee that the setter triggers a re-render.

    Change this:

    const onDelete = (event, key) => {
        let newCategories = categories; // this line is the problem
        delete newCategories[key];
        setCategories(newCategories);
    };
    

    to instead be:

    const onDelete = (event, key) => {
        // create a new object rather than mutating existing object
        let newCategories = { ...categories }; 
        delete newCategories[key];
        setCategories(newCategories);
    };