{"record":{"id":"87153a6e59becc42","repo":"dotnet/aspnetcore","slug":"cannot-connect-component-this-localname-to-the","errorCode":null,"errorMessage":"Cannot connect component ${this.localName} to the document after it has been disposed.","messagePattern":"Cannot connect component (.+?) to the document after it has been disposed\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/Components/CustomElements/src/js/BlazorCustomElements.ts","lineNumber":70,"sourceCode":"      Object.defineProperty(this, camelCase(dotNetName), {\n        get: () => this._parameterValues[dotNetName],\n        set: newValue => {\n          if (this.hasAttribute(attributeName)) {\n            // It's nice to keep the DOM in sync with the properties. This set a string representation\n            // of the value, but this will get overwritten with the original typed value before we send it to .NET\n            this.setAttribute(attributeName, newValue);\n          }\n\n          this._parameterValues[dotNetName] = newValue;\n          this._supplyUpdatedParameters();\n        }\n      });\n    }\n  }\n\n  connectedCallback() {\n    if (this._isDisposed) {\n      throw new Error(`Cannot connect component ${this.localName} to the document after it has been disposed.`);\n    }\n\n    clearTimeout(this._disposalTimeoutHandle);\n  }\n\n  disconnectedCallback() {\n    this._disposalTimeoutHandle = setTimeout(async () => {\n      this._isDisposed = true;\n      const rootComponent = await this._addRootComponentPromise;\n      rootComponent.dispose();\n    }, 1000);\n  }\n\n  attributeChangedCallback(name: string, oldValue: string, newValue: string) {\n    const parameterInfo = this._attributeMappings[name];\n    if (parameterInfo) {\n      this._parameterValues[parameterInfo.name] = BlazorCustomElement.parseAttributeValue(newValue, parameterInfo.type, parameterInfo.name);\n      this._supplyUpdatedParameters();","sourceCodeStart":52,"sourceCodeEnd":88,"githubUrl":"https://github.com/dotnet/aspnetcore/blob/3600ca084e9c8b5f4174fc5e747f4c52d2100806/src/Components/CustomElements/src/js/BlazorCustomElements.ts#L52-L88","documentation":"Thrown by BlazorCustomElement.connectedCallback when a custom element is re-inserted into the DOM after its disposal timer has already fired. disconnectedCallback schedules disposal after a 1000ms delay; once that callback runs, _isDisposed becomes true and the underlying .NET root component is disposed. Reconnecting the same element node after that point cannot resurrect a disposed .NET component, so the framework refuses rather than silently breaking.","triggerScenarios":"An element is removed from the DOM (e.g. via a parent removal, conditional render, or list diff) and then re-added more than 1000ms later, OR the same element node is moved between documents after disposal. Also triggered by re-rendering logic that detaches and reattaches the same node across a long pause.","commonSituations":"Drag-and-drop that detaches/re-attaches nodes; virtualized lists that recycle nodes after idle periods; SPA route transitions that physically remove and later restore a host element; tabs/accordions that toggle display:none vs actual DOM removal with delayed reconnect.","solutions":["Render a fresh element instance instead of reusing the disposed node: let Blazor diff create a new custom element rather than moving the old DOM node.","Avoid removing the element from the DOM if it will be needed again; prefer hiding via CSS (display:none) over detach.","If you must detach/reattach within the same lifetime, ensure the reconnection happens within the 1000ms disposal window — but treat this as fragile and prefer recreating the element.","Audit framework-level node recycling (e.g. virtualization components) to confirm they create new elements rather than reusing detached ones."],"exampleFix":"// before: moving a disposed node back into the DOM\ncontainer.appendChild(existingElement); // throws if _isDisposed\n\n// after: create a new element so Blazor registers a fresh root component\nconst fresh = document.createElement(existingElement.localName);\n// copy over the attributes you need\nfor (const attr of existingElement.attributes) fresh.setAttribute(attr.name, attr.value);\ncontainer.appendChild(fresh);","handlingStrategy":"validation","validationCode":"// Before reattaching a previously-removed element, check whether disposal already fired.\nfunction safeReattach(parent, node) {\n  if (node._isDisposed) {\n    // Recreate a fresh element instead of reusing the disposed one.\n    const fresh = document.createElement(node.localName);\n    for (const a of node.attributes) fresh.setAttribute(a.name, a.value);\n    parent.appendChild(fresh);\n    return fresh;\n  }\n  parent.appendChild(node);\n  return node;\n}","typeGuard":"// Framework element is a BlazorCustomElement; the guard checks the disposal flag.\nfunction isDisposedBlazorElement(node) {\n  return node instanceof HTMLElement && node._isDisposed === true;\n}","tryCatchPattern":"try {\n  container.appendChild(existingElement);\n} catch (e) {\n  if (/has been disposed/.test(e.message)) {\n    // Fall back to a freshly created element.\n    const fresh = document.createElement(existingElement.localName);\n    container.appendChild(fresh);\n  } else {\n    throw e;\n  }\n}","preventionTips":["Prefer creating new elements over moving old nodes between parents.","Hide elements with CSS instead of detaching when they may be needed again.","If you must detach/reattach, do so well within the 1000ms disposal window.","Audit virtualization and list-diffing code to confirm it does not recycle disposed nodes."],"tags":["blazor-custom-elements","lifecycle","dom","disposal"],"backgroundTag":null,"analyzedSha":"3600ca084e9c8b5f4174fc5e747f4c52d2100806","analyzedAt":"2026-08-11T16:32:30.678Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}