{"record":{"id":"26b1d5b1841d75fb","repo":"dotnet/aspnetcore","slug":"interop-methods-are-already-registered-for-rendere","errorCode":null,"errorMessage":"Interop methods are already registered for renderer ${rendererId}","messagePattern":"Interop methods are already registered for renderer (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/Components/Web.JS/src/Rendering/WebRendererInteropMethods.ts","lineNumber":27,"sourceCode":"const rendererAttachedListeners: ((browserRendererId: number) => void)[] = [];\nconst rendererByIdResolverMap: Map<number, [() => void | undefined, Promise<void> | undefined]> = new Map();\n\nexport function attachRendererIdResolver(rendererId: number, resolver: () => void | undefined, promise: Promise<void> | undefined) {\n  rendererByIdResolverMap.set(rendererId, [resolver, promise]);\n}\n\nexport function getRendererAttachedPromise(rendererId: number): Promise<void> | undefined {\n  return rendererByIdResolverMap.get(rendererId)?.[1];\n}\n\nexport function attachWebRendererInterop(\n  rendererId: number,\n  interopMethods: DotNet.DotNetObject,\n  jsComponentParameters?: JSComponentParametersByIdentifier,\n  jsComponentInitializers?: JSComponentIdentifiersByInitializer,\n): void {\n  if (interopMethodsByRenderer.has(rendererId)) {\n    throw new Error(`Interop methods are already registered for renderer ${rendererId}`);\n  }\n\n  interopMethodsByRenderer.set(rendererId, interopMethods);\n\n  if (jsComponentParameters && jsComponentInitializers && Object.keys(jsComponentParameters).length > 0) {\n    const manager = getInteropMethods(rendererId);\n    enableJSRootComponents(rendererId, manager, jsComponentParameters, jsComponentInitializers);\n  }\n\n  rendererByIdResolverMap.get(rendererId)?.[0]?.();\n\n  invokeRendererAttachedListeners(rendererId);\n}\n\nexport function detachWebRendererInterop(rendererId: number): DotNet.DotNetObject {\n  const interopMethods = interopMethodsByRenderer.get(rendererId);\n  if (!interopMethods) {\n    throw new Error(`Interop methods are not registered for renderer ${rendererId}`);","sourceCodeStart":9,"sourceCodeEnd":45,"githubUrl":"https://github.com/dotnet/aspnetcore/blob/3600ca084e9c8b5f4174fc5e747f4c52d2100806/src/Components/Web.JS/src/Rendering/WebRendererInteropMethods.ts#L9-L45","documentation":"Thrown by attachWebWidgetInterop when interopMethodsByRenderer already has an entry for the given rendererId. Each renderer should attach its interop bridge exactly once; a second attachment indicates a double-initialization bug. The map is keyed by rendererId, so the same id registering twice is the trigger.","triggerScenarios":"Blazor.start being called twice; a circuit reconnect path that re-attaches without first detaching; two interactive renderers created with the same id; a misconfigured custom host that calls attachWebRendererInterop manually more than once.","commonSituations":"Hot reload re-running initialization; a custom hosting layer that manually drives Blazor lifecycle and calls attach twice; testing harness that doesn't tear down between runs.","solutions":["Call detachWebRendererInterop(rendererId) before re-attaching for the same id.","Ensure Blazor.start is invoked exactly once per page.","Avoid sharing a rendererId across multiple renderer instances; let the framework allocate ids.","In reconnect logic, follow the framework's detach->attach sequence rather than attach->attach."],"exampleFix":"// before (throws on second call)\nattachWebRendererInterop(1, interop, params, inits);\nattachWebRendererInterop(1, interop2, params, inits);\n\n// after\ndetachWebRendererInterop(1);\nattachWebRendererInterop(1, interop2, params, inits);","handlingStrategy":"validation","validationCode":"if (interopMethodsByRenderer.has(rendererId)) {\n  // already attached — detach first if this is a legitimate re-attach\n  detachWebRendererInterop(rendererId);\n}\nattachWebRendererInterop(rendererId, interop, params, inits);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Attach interop exactly once per rendererId.","In reconnect scenarios, detach before re-attaching.","Let the framework manage rendererIds; don't hard-code them."],"tags":["blazor","interop","renderer","lifecycle","duplicate"],"backgroundTag":null,"analyzedSha":"3600ca084e9c8b5f4174fc5e747f4c52d2100806","analyzedAt":"2026-08-11T16:32:30.678Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}