Summary
@openuidev/react-lang@0.2.1's Renderer can crash the host app with a React commit-phase error while re-rendering or streaming content:
NotFoundError: Failed to execute 'insertBefore' on 'Node':
The node before which the new node is to be inserted is not a child of this node.
The error is thrown deep in React's commit (mutation) phase, so it cannot be caught by a nested error boundary in the host app — it escalates to the app root and unmounts the whole subtree. We hit it in production in a streaming chat UI, and also when a sibling re-render forces an already-rendered Renderer subtree to re-render.
Root cause
Two interacting behaviors in the renderer (line refs from the published dist/index.mjs, v0.2.1):
1. ElementErrorBoundary re-presents stale children on error. On hasError, render() returns this.lastValidChildren — React element objects captured from a previous render (componentDidMount/componentDidUpdate stash this.props.children). Those elements correspond to DOM nodes React has already reconciled/moved. Re-inserting them makes React's fiber tree disagree with the live DOM:
// dist/index.mjs ~L443-471
componentDidUpdate(prevProps) {
if (!this.state.hasError) this.lastValidChildren = this.props.children;
if (this.state.hasError && prevProps.children !== this.props.children) this.setState({ hasError: false });
}
render() {
if (this.state.hasError) return this.lastValidChildren; // <-- stale element tree
return this.props.children;
}
2. Array children are keyed by array index. renderDeep renders arrays as index-keyed fragments:
// dist/index.mjs ~L482
if (Array.isArray(value)) return value.map((v, i) => jsx(Fragment, { children: renderDeep(v) }, i));
When the parsed structure at a given index changes type between renders (which happens constantly while streaming a partial DSL, or when a child throws and the boundary swaps in stale children and back), index keys make React reconcile mismatched subtrees. Combined with (1), React's DOM bookkeeping desyncs and insertBefore(newNode, referenceNode) fails because referenceNode is no longer a child of the expected parent.
Reproduction (essence)
- Render a moderately deep tree (e.g. a Card with an array of child elements) via
<Renderer>.
- Cause a child component to throw intermittently during re-render — e.g. stream a DSL so it is repeatedly re-parsed while partially formed, so a component occasionally receives malformed/partial props and throws.
ElementErrorBoundary catches, returns stale lastValidChildren, the next valid render swaps back — at index-keyed positions whose types shift.
- Intermittently:
NotFoundError: Failed to execute 'insertBefore' ... in React's commit phase, which escapes to the app root.
Suggested fixes
ElementErrorBoundary on error should NOT return stale element instances. Return null (or a caller-supplied fallback), or reset via a key bump so the subtree cleanly remounts. Re-inserting elements whose DOM React already moved is the core hazard.
- Prefer stable keys over array index in
renderDeep where a stable identity is derivable from the node, so type changes at a position remount cleanly rather than reconciling mismatched subtrees.
Workaround we're using
A local pnpm patch changing ElementErrorBoundary.render() to return null on error. This eliminated the crash across streaming and re-render triggers, at the cost of losing the "show last good state" behavior for a single element for one frame (acceptable vs. an app crash).
Environment
@openuidev/react-lang 0.2.1
- React 19.2, Next.js 16
Summary
@openuidev/react-lang@0.2.1'sRenderercan crash the host app with a React commit-phase error while re-rendering or streaming content:The error is thrown deep in React's commit (mutation) phase, so it cannot be caught by a nested error boundary in the host app — it escalates to the app root and unmounts the whole subtree. We hit it in production in a streaming chat UI, and also when a sibling re-render forces an already-rendered
Renderersubtree to re-render.Root cause
Two interacting behaviors in the renderer (line refs from the published
dist/index.mjs, v0.2.1):1.
ElementErrorBoundaryre-presents stale children on error. OnhasError,render()returnsthis.lastValidChildren— React element objects captured from a previous render (componentDidMount/componentDidUpdatestashthis.props.children). Those elements correspond to DOM nodes React has already reconciled/moved. Re-inserting them makes React's fiber tree disagree with the live DOM:2. Array children are keyed by array index.
renderDeeprenders arrays as index-keyed fragments:When the parsed structure at a given index changes type between renders (which happens constantly while streaming a partial DSL, or when a child throws and the boundary swaps in stale children and back), index keys make React reconcile mismatched subtrees. Combined with (1), React's DOM bookkeeping desyncs and
insertBefore(newNode, referenceNode)fails becausereferenceNodeis no longer a child of the expected parent.Reproduction (essence)
<Renderer>.ElementErrorBoundarycatches, returns stalelastValidChildren, the next valid render swaps back — at index-keyed positions whose types shift.NotFoundError: Failed to execute 'insertBefore' ...in React's commit phase, which escapes to the app root.Suggested fixes
ElementErrorBoundaryon error should NOT return stale element instances. Returnnull(or a caller-supplied fallback), or reset via akeybump so the subtree cleanly remounts. Re-inserting elements whose DOM React already moved is the core hazard.renderDeepwhere a stable identity is derivable from the node, so type changes at a position remount cleanly rather than reconciling mismatched subtrees.Workaround we're using
A local
pnpm patchchangingElementErrorBoundary.render()toreturn nullon error. This eliminated the crash across streaming and re-render triggers, at the cost of losing the "show last good state" behavior for a single element for one frame (acceptable vs. an app crash).Environment
@openuidev/react-lang0.2.1