0

I have 2 way bindings for backbone.marionette and react

  • reactToMarionette
  • useMarionetteInReact hook

when root/parent component gets rendered/destroyed React gives this 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.

I am looking for ways to fix this warning

I think ReactChild node is somehow marked as toBeRendered at the app render, even though I would expect that app.root would not know about nested/inserted MView react root

here is the code that I use in playground

link: https://codesandbox.io/s/my-test-adapters-forked-z2qxfk

import { View } from "backbone.marionette";
import React, { useCallback, useRef, useState } from "react";

import { createRoot } from "react-dom/client";

export function App() {
  // create a reason to render inner component
  const [isVisible, setVisible] = useState(true);
  const toggle = useCallback(() => setVisible((i) => !i), []);

  console.log("render app", isVisible);
  return (
    <>
      <button onClick={toggle}>{`toggle: ${isVisible}`}</button>
      {isVisible ? <SomeComponent /> : null}
    </>
  );
}

// component that children are controlled from outside (by marionette)
const SomeComponent = () => {
  console.log("render SomeComponent");
  const ref = useMarionetteInReact();
  return <div ref={ref} className="stable-react-div"></div>;
};

// hook, for rendering marionette view
const useMarionetteInReact = () => {
  const viewRef = useRef(null);

  const divRef = useCallback((el) => {
    if (el === null) {
      console.log("MView destroy in useCallback", viewRef.current);
      viewRef.current && viewRef.current.destroy();
    } else {
      console.log("created MView");
      const MView = new reactToMarionette({
        className: "reactToMarionette",
        template: false,
        component: <ReactChild />
      });
      viewRef.current = MView;
      MView.render();
      el.appendChild(MView.el);
    }
  }, []);

  return divRef;
};

class reactToMarionette extends View {
  constructor(options) {
    super(options);
    this.component = options.component;
    this.el.textContent = "I am Marionette";

    console.log("create root", this.el);
    this.divEl = document.createElement("div");
    this.divEl.classList.add("portal-root");
    this.root = createRoot(this.divEl);
    this.el.append(this.divEl);
  }

  render() {
    console.log("MView render");
    this.root.render(this.component);
  }

  onBeforeDestroy() {
    console.log("onBeforeDestroy", this.root);
    if (this.root) {
      // setTimeout(() => this.root.unmount());
      this.root.unmount();
    }
  }
}

const ReactChild = () => {
  console.log("render ReactChild");
  return <div> Hello, I am react child </div>;
};


console output

index.js:27 render app true
14:28:49.848 index.js:27 render SomeComponent
14:28:49.849 index.js:27 created MView
14:28:49.849 index.js:27 create root <div class=​"reactToMarionette">​…​</div>​
14:28:49.849 index.js:27 MView render
14:28:49.850 index.js:27 render ReactChild
14:28:51.846 index.js:27 render app true
14:28:51.846 index.js:27 render SomeComponent
14:28:51.846 index.js:27 created MView
14:28:51.846 index.js:27 create root <div class=​"reactToMarionette">​…​</div>​
14:28:51.847 index.js:27 MView render
14:28:51.847 index.js:27 render ReactChild
14:28:51.847 index.js:27 render app false
14:28:51.847 index.js:27 MView destroy in useCallback reactToMarionette
14:28:51.848 index.js:27 onBeforeDestroy ReactDOMRoot
14:28:51.848 index.js:27 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.
    at App (https://ocdkn0.csb.app/src/_app.js:31:41) 
    in App
Alexander
  • 105
  • 1
  • 2
  • 16
  • 1
    You commented on https://stackoverflow.com/questions/73459382/react-18-async-way-to-unmount-root about this issue. I put an answer there. It doesn't use class components, but the concept should be transferable. I guess [this codesandbox](https://codesandbox.io/s/my-test-adapters-forked-u5nh4d?file=/src/_app.js) works. – konqi Dec 01 '22 at 17:08

0 Answers0