DiagView
DiagView React StrictMode
← Hub

React 18 + StrictMode

A real React tree (development build, StrictMode on) mounting, unmounting, remounting and replacing DiagView diagrams. Any unexpected error would show up in a red box below; the deliberate "unsafe pattern" crash stays inside the viewer card.

⚛ React 18.3 dev build StrictMode double-effects floating + header + off layouts
effect runs (init calls)0
DiagView initialized…
toolbar wrappers in DOM0
unexpected errors0


        
⚛

What StrictMode does here

In development, React StrictMode runs every effect twice: mount, cleanup, mount again. For DiagView that means init() → destroy() → init() in a row. The "effect runs" counter above shows 2 after load. Older builds bailed on the second init and ended up with nothing initialized; now init waits for the pending destroy. Diagrams initialise lazily as they scroll near the viewport, so "wrappers" reads 2 (floating + header) once the viewer cards below are in view.

🔁

Unmount / Mount / Replace

Unmount removes the floating and header viewer components; the lifecycle cleanup calls destroy(), so "initialized" flips to NO on purpose, the wrapper count drops to 0 and the off-layout diagram loses its click handler too. Mount brings them back and DiagView re-initializes everything. Replace swaps Diagram A for B by re-keying the container, never the diagram element itself. All three must run with zero errors.

🧩

Why Unmount removes the diagram itself

Unmounting is React deleting the component and every DOM node it rendered, the diagram included. That is what unmount means in any framework, in every layout: the off-layout card only survives here because it is never unmounted. DiagView does not delete anything; its toolbar simply leaves together with the diagram it was attached to. There is nothing to fix. If you want the diagrams to stay and only DiagView to go, do not unmount: call destroy() instead. Detach DiagView does exactly that: the toolbars vanish, the diagrams remain plain SVG, and Attach calls init() to enhance them again.

⚠️

The "unsafe pattern" checkbox

Ticking it re-renders the floating and header viewers with the .diagram element as the component root, with no container around it. Both layouts move that element into a wrapper, so when you then click Unmount, React asks the parent it remembers to remove a node that is no longer there. Expect a red NotFoundError: The node to be removed is not a child of this node inside each of those two cards. The off-layout card never re-parents, so it is unaffected. Each card has its own React error boundary, so the crash stays in the card: click Mount viewers and DiagView re-initialises (untick first for the safe pattern). Without a boundary React 18 would unmount the entire root and only a page refresh would bring it back.

Why the container matters

// SAFE — what React rendered            // what the DOM looks like after DiagView init
<div class="diagram-host">               <div class="diagram-host">          ← React removes this
  <div class="diagram">…</div>             <div class="diagview-wrapper">
</div>                                     <div class="diagview-viewport">
                                              <div class="diagram">…</div>   ← goes away with it
                                          …

// UNSAFE — what React rendered          // after DiagView init
<div class="diagram">…</div>               <div class="diagview-wrapper">
                                            <div class="diagview-viewport">
                                              <div class="diagram">…</div>   ← React still thinks this is
                                                                                a child of the original parent
// On unmount React calls originalParent.removeChild(diagram) → NotFoundError

The one rule

// Keep the diagram element inside a container the framework owns.
// floating/header layouts move .diagram into a toolbar wrapper; React must
// never remove that exact element itself — removing the container is safe.
function Viewer({ svg }) {
  useEffect(() => {
    DiagView.init({ layout: "floating" });
    return () => DiagView.destroy();   // StrictMode: init() queues behind this
  }, []);

  return (
    <div className="diagram-host">                      // React owns this
      <div className="diagram" dangerouslySetInnerHTML={{ __html: svg }} />
    </div>
  );
}

// layout "off" never touches the surrounding DOM, so .diagram may be the root.
<div className="diagram" data-diagview-layout="off">…</div>