I have a rare usecase where I need to register multiple roots in my React Component and destroy them when the component unmounts. Obviously, it happens that I unmount a root when its rendering. I simulated that case by calling root.unmount()
right after root.render(...)
. in the following example: https://codesandbox.io/s/eager-grothendieck-h49eoo?file=%2Fsrc%2FApp.tsx
This results in the following warning:
Warning: Attempted to synchronously unmount a root while React was already rendering. React cannot finish unmounting the root until the current render has completed, which may lead to a race condition.
This warning implies to me that there is an async way to unmount a root, but I coouldn't find out how. Wrapping root.unmount()
in an async function (const unmount = async () => root.unmount()
) did not work. Any ideas? Am I getting something totally wrong here?
In our project we're unmounting asynchronously via setTimeout
. I created an updated codesandbox, you can find the working example here.
The snippet below shows how the mounting and unmounting can be handled. Note that mounting and unmounting are detached from the synchronous rendering loop via setTimeout
, this is to avoid the racing between synchronous mount and asynchronous unmount. Otherwise it can happen that the component is unmounted right after it has been mounted from a previous render.
I'm not convinced this is the best solution, but it's working for us so far.
import { useRef } from 'react'
import * as ReactDOM from 'react-dom/client'
function MyComponent() {
const containerRef = useRef<HTMLDivElement | null>(null);
const rootRef = useRef<ReactDOM.Root>();
useEffect(() => {
const renderTimeout = setTimeout(() => {
if (containerRef.current) {
console.log("create root");
rootRef.current =
rootRef.current ?? ReactDOM.createRoot(containerRef.current);
}
if (containerRef.current && rootRef.current) {
console.log("component render");
rootRef.current.render(<div>mounted component</div>);
}
});
return () => {
clearTimeout(renderTimeout);
console.log("unmount");
const root = rootRef.current;
rootRef.current = undefined;
setTimeout(() => {
console.log("component unmount");
root?.unmount();
});
};
}, [rootRef]);
return <div ref={containerRef}></div>;
}