React 18 in StrictMode
A real React tree (development build, StrictMode on) mounting, unmounting, remounting and replacing DiagView diagrams. Any unexpected error shows up in a red box below. The deliberate "unsafe pattern" crash stays inside its viewer card.
Live diagrams
What this page tests
Four things a component framework does to a diagram, and what DiagView does in each case.
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 and header) once the viewer cards are in view.
Unmount, mount and 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 leaves together with the diagram it was attached to. 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 inside each of those two cards. The message
after it depends on the browser. 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
Floating and header layouts move the diagram into a wrapper. React must only ever remove an element it still owns.
<div class="diagram-host">
<div class="diagram">...</div>
</div>
<div class="diagram-host">
<div class="diagview-wrapper">
<div class="diagview-viewport">
<div class="diagram">...</div>
...
// React removes .diagram-host,
// and .diagram goes away with it
<div class="diagram">...</div>
<div class="diagview-wrapper">
<div class="diagview-viewport">
<div class="diagram">...</div>
// React still thinks .diagram is
// a child of the original parent
// React calls
originalParent.removeChild(diagram)
// and gets a NotFoundError
The one rule
// Keep .diagram inside a container
// the framework owns. Floating and
// header layouts move .diagram into
// a toolbar wrapper, so React must
// never remove that element itself.
// Removing the container is safe.
function Viewer({ svg }) {
useEffect(() => {
DiagView.init({
layout: "floating"
});
// StrictMode: init() waits
// for this destroy()
return () => DiagView.destroy();
}, []);
return (
// React owns this container
<div className="diagram-host">
<div
className="diagram"
dangerouslySetInnerHTML={{
__html: svg
}}
/>
</div>
);
}
// Layout "off" never touches the
// surrounding DOM, so .diagram may
// be the component root.
<div
className="diagram"
data-diagview-layout="off"
>...</div>