Search code examples
javascriptreactjstypescriptwebreact-hooks

React synchronously remove an item from the dom


I need to synchronously remove a child from the container. I created a simplified code to show my current solution (Which makes use of normal pattern using useState hook)

type ChildProps = {
  index: number;
  id: string;
  remove: (index: number) => void;
};

function Child(props: ChildProps) {
  const handleClick = () => {
    console.log('FIRST');
    props.remove(props.index);
    console.log('THIRD');
  };
  return (
    <button id={props.id} onClick={handleClick}>
      Child {props.index}
    </button>
  );
}

export default function Home() {
  const [childrens, setChildrens] = React.useState<{ el: React.ReactNode; index: number }[]>([]);

  const removeChildren = (index: number) => {
    setChildrens((old) => old.filter((e) => e.index !== index));
  };

  const addChildren = () => {
    const index = new Date().getTime();
    setChildrens((old) => [
      ...old,
      { index, el: <Child id={`${index}`} index={index} remove={removeChildren} /> },
    ]);
  };

  console.log('SECOND');
  return (
    <div>
      <button onClick={addChildren}>Add</button>
      <div>{childrens.map((e) => e.el)}</div>
    </div>
  );
}

When the remove handler is called, FIRST is printed, then the remove function is called (which dispatches the parent component's status update), then THIRD is printed, and finally SECOND will be printed when the parent component is updated which will "remove" the child.

I need the component to be removed on the next instruction after the removal method call.

In my application in the child components I make use of Tanstack Query hooks to use the data from the api.

After calling a delete endpoint I delete the component and invalidate the cache.

The problem is that the cache is invalidated before the component is unmounted and this causes the endpoint to be called to get the component data (because the Tanstack hook is still active since the component is still present in the DOM) this causes an error in the endpoint call as the entry no longer exists.

I've tried looking for ways to manipulate the react virtual DOM but haven't found anything useful.

Manipulating the DOM directly is not a good option

document.getElementById(`${index}`)?.remove()

This causes the following exception: Uncaught DOMException: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node


Solution

  • I found the answer into React new docs.

    To fix this issue, you can force React to update (“flush”) the DOM synchronously. To do this, import flushSync from react-dom and wrap the state update into a flushSync call

    Link to the doc