dotnet/aspnetcore · error · Error
Cannot connect component ${this.localName} to the document a
Error message
Cannot connect component ${this.localName} to the document after it has been disposed. What it means
Thrown from BlazorCustomElement.connectedCallback when the custom element is re-attached to the DOM after its disposal timer has already fired. BlazorCustomElement sets _isDisposed=true inside disconnectedCallback's setTimeout(1000) and then disposes the underlying Blazor root component, so re-inserting the same node back into the document is no longer legal because the .NET root component it was bound to no longer exists.
Source
Thrown at src/Components/CustomElements/src/js/BlazorCustomElements.ts:70
Object.defineProperty(this, camelCase(dotNetName), {
get: () => this._parameterValues[dotNetName],
set: newValue => {
if (this.hasAttribute(attributeName)) {
// It's nice to keep the DOM in sync with the properties. This set a string representation
// of the value, but this will get overwritten with the original typed value before we send it to .NET
this.setAttribute(attributeName, newValue);
}
this._parameterValues[dotNetName] = newValue;
this._supplyUpdatedParameters();
}
});
}
}
connectedCallback() {
if (this._isDisposed) {
throw new Error(`Cannot connect component ${this.localName} to the document after it has been disposed.`);
}
clearTimeout(this._disposalTimeoutHandle);
}
disconnectedCallback() {
this._disposalTimeoutHandle = setTimeout(async () => {
this._isDisposed = true;
const rootComponent = await this._addRootComponentPromise;
rootComponent.dispose();
}, 1000);
}
attributeChangedCallback(name: string, oldValue: string, newValue: string) {
const parameterInfo = this._attributeMappings[name];
if (parameterInfo) {
this._parameterValues[parameterInfo.name] = BlazorCustomElement.parseAttributeValue(newValue, parameterInfo.type, parameterInfo.name);
this._supplyUpdatedParameters();View on GitHub (pinned to 294cab2f9b)
Solutions
- If you must re-add the element, create a NEW element instance instead of reusing the disposed node (let the framework instantiate a fresh <my-component>).
- Ensure the element is re-inserted within the 1000ms disposal grace window so connectedCallback's clearTimeout cancels the disposal (the timeout is in disconnectedCallback at BlazorCustomElements.ts:77).
- Detach by moving the element into a hidden container rather than fully removing it from the document, so disconnectedCallback never fires.
- Call delete/cloneNode(false) and re-set attributes to obtain a fresh, undisposed custom element before re-insertion.
Example fix
// before: reusing a removed element after >1s
container.removeChild(myBlazorElement);
// ... >1000ms later ...
container.appendChild(myBlazorElement); // throws: already disposed
// after: create a fresh element
const fresh = document.createElement(myBlazorElement.localName);
fresh.setAttribute('some-param', 'value');
container.appendChild(fresh); Defensive patterns
Strategy: try-catch
Validate before calling
// There is no public accessor for _isDisposed (private). Track externally.
const removedAt = Date.now();
container.removeChild(el);
// only re-add within the 1000ms disposal grace window
if (Date.now() - removedAt < 1000) {
container.appendChild(el);
} else {
const fresh = document.createElement(el.localName);
// copy attributes
for (const attr of el.attributes) fresh.setAttribute(attr.name, attr.value);
container.appendChild(fresh);
} Type guard
// BlazorCustomElement exposes no public 'disposed' flag.
// Duck-type by trying a no-op setParameter; disposed elements reject updates.
function isBlazorElementAlive(el: HTMLElement): boolean {
return el.isConnected && !(el as unknown as { _isDisposed?: boolean })._isDisposed;
} Try / catch
try {
container.appendChild(existingElement);
} catch (e) {
if (/after it has been disposed/.test((e as Error).message)) {
const fresh = document.createElement(existingElement.localName);
for (const a of existingElement.attributes) fresh.setAttribute(a.name, a.value);
container.appendChild(fresh);
} else {
throw e;
}
} Prevention
- Never re-insert a Blazor custom element after it has been detached for more than 1 second.
- Prefer creating a fresh element over recycling a removed one.
- Move elements to a hidden node instead of detaching them, to avoid triggering disposal.
- Keep a 1-second mental budget: disconnectedCallback schedules disposal after 1000ms.
When it happens
Trigger: A registered Blazor custom element is removed from the DOM and re-added more than 1000ms later (e.g. via conditional rendering that recreates the element, a virtual-list that recycles nodes, or a framework like Lit/React that moves DOM nodes). Re-insertion calls connectedCallback, which sees _isDisposed===true and throws.
Common situations: Toggling a container with *ngIf / React conditional that detaches the same element instance for over a second; dragging-and-dropping a custom element across containers with a delay; SPA frameworks that unmount then remount subtrees on navigation; using element.removeNode() followed by appendChild() in an async handler.
Related errors
- Invalid boolean value '${attributeValue}' for parameter '${p
- Invalid number value '${attributeValue}' for parameter '${pa
- The parameter '${parameterName}' accepts a complex-typed obj
- Unknown type '${type}' for parameter '${parameterName}'
- Unable to focus an SVG element that does not have a tabindex
AI-assisted analysis of dotnet/aspnetcore@294cab2f9b (2026-08-06).
Data as JSON: /api/errors/87153a6e59becc42.
Report an issue: GitHub.