can1357/oh-my-pi · error · Error

The node to remove is not a child of this node

Error message

The node to remove is not a child of this node

What it means

Node.removeChild looks up the child in `this.childNodes` and throws when it is not found (indexOf === -1). A node can only be removed from its actual current parent; calling removeChild with a node that is detached or parented elsewhere throws this error, matching the standard DOM's NotFoundError.

Source

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

		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 = [];
		this.append(...children);
	}

	/** Append nodes or strings. */
	append(...children: Array<Node | string>): void {
		for (const child of children) {
			this.appendChild(typeof child === "string" ? this.documentForCreation().createTextNode(child) : child);
		}
	}

View on GitHub (pinned to 9690622007)

Solutions

  1. Check `child.parentNode` first and remove from `child.parentNode` (or skip if null) instead of a captured parent reference.
  2. Guard double removals: `if (child.parentNode) child.parentNode.removeChild(child);`
  3. If iterating while removing, iterate over a snapshot `[...parent.childNodes]` or loop by index from the end.
  4. Wrap disposal logic so it is idempotent — mark nodes as removed or rely on parentNode nullness.

Example fix

// before: removes from captured parent, may double-remove
parent.removeChild(child); // throws if child moved or already removed

// after: remove from the node's actual parent, idempotently
child.parentNode?.removeChild(child);
Defensive patterns

Strategy: validation

Validate before calling

if (child.parentNode) {
  child.parentNode.removeChild(child);
}

Type guard

function isAttachedChild(parent: Node, child: Node): child is Node & { parentNode: Node } {
  return parent.childNodes.includes(child);
}

Try / catch

try {
  parent.removeChild(child);
} catch (err) {
  if (err instanceof Error && err.message === "The node to remove is not a child of this node") {
    // idempotent cleanup: already removed or moved — ignore
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: Calling `parent.removeChild(child)` where `child.parentNode !== parent` — child already removed (double removal), child belongs to another parent, or child/parent arguments are inconsistent; also reached indirectly via replaceChild, which calls removeChild internally.

Common situations: Cleanup loops that remove the same node twice (e.g. in clear + dispose paths); removing a node from a captured parent after the node was re-parented; removing nodes during iteration over childNodes without accounting for mutation; event/dispose handlers racing each other.

Related errors


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