can1357/oh-my-pi · error · Error

The node to replace is not a child of this node

Error message

The node to replace is not a child of this node

What it means

Node.replaceChild(child, previous) requires `previous` to be an existing direct child of the node; `this.childNodes.indexOf(previous)` returning -1 throws this error before any mutation. This mirrors the standard DOM NotFoundError behavior — you can only replace a node that is actually a child of the receiver.

Source

Thrown at packages/utils/src/dom/core.ts:228

	insertBefore<T extends Node>(child: T, reference: Node | null): T {
		if (reference === null) return this.appendChild(child);
		const index = this.childNodes.indexOf(reference);
		if (index < 0) throw new Error("The reference node is not a child of this node");
		if (child instanceof DocumentFragment) {
			for (const nested of [...child.childNodes]) this.insertBefore(nested, reference);
			return child;
		}
		child.parentNode?.removeChild(child);
		child.parentNode = this;
		child.setOwnerDocument(this.documentForCreation());
		this.childNodes.splice(index, 0, child);
		return child;
	}

	/** Replace a current child with another node. */
	replaceChild<T extends Node>(child: Node, previous: T): T {
		const index = this.childNodes.indexOf(previous);
		if (index < 0) throw new Error("The node to replace is not a child of this node");
		this.removeChild(previous);
		this.insertBefore(child, this.childNodes[index] ?? null);
		return previous;
	}

	/** Remove a current child. */
	removeChild<T extends Node>(child: T): T {
		const index = this.childNodes.indexOf(child);
		if (index < 0) throw new Error("The node to remove is not a child of this node");
		this.childNodes.splice(index, 1);
		child.parentNode = null;
		return child;
	}

	/** Replace all children with nodes or strings. */
	replaceChildren(...children: Array<Node | string>): void {
		for (const child of this.childNodes) child.parentNode = null;
		this.childNodes = [];

View on GitHub (pinned to 9690622007)

Solutions

  1. Assert `oldChild.parentNode === parent` before replaceChild; if it's null, the node is already detached.
  2. Check argument order: signature is replaceChild(newChild, oldChild) — the node being replaced goes second.
  3. If the node may already be gone, guard with a membership check or catch and fall back to appendChild/removeChild accordingly.
  4. Re-query the live child (by index or querySelector) instead of reusing a node captured before prior mutations.

Example fix

// before: swapped/invalid previous
parent.replaceChild(oldChild, newChild); // throws: oldChild is not a child

// after: correct order plus guard
if (oldChild.parentNode === parent) {
  parent.replaceChild(newChild, oldChild);
}
Defensive patterns

Strategy: validation

Validate before calling

if (oldChild.parentNode === parent) {
  parent.replaceChild(newChild, oldChild);
}

Type guard

function isReplaceable(parent: Node, oldChild: Node): boolean {
  return parent.childNodes.includes(oldChild);
}

Try / catch

try {
  parent.replaceChild(newChild, oldChild);
} catch (err) {
  if (err instanceof Error && err.message === "The node to replace is not a child of this node") {
    // already detached or wrong parent — decide: append instead or skip
    parent.appendChild(newChild);
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: Calling `parent.replaceChild(newChild, oldChild)` where `oldChild` was already removed, was never a child of `parent`, or belongs to another parent/subtree; calling with swapped arguments so the 'previous' slot holds the new node.

Common situations: Replacing a node after a prior removal/move made the reference stale; using a node from a cloned/detached subtree; argument-order mistakes (child vs previous) in code ports from other DOM implementations; batch updates that replace the same node twice.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/7b07a028994a74ca. Report an issue: GitHub.