dotnet/aspnetcore · error

Interop methods are not registered for renderer ${rendererId

Error message

Interop methods are not registered for renderer ${rendererId}

What it means

Thrown by detachWebRendererInterop when you try to detach (dispose) a renderer whose interop object was never registered, or was already detached. The function reads interopMethodsByRenderer.get(rendererId) and, on a miss, throws. Blazor uses this to keep its renderer-interop map consistent; calling detach twice or detaching an unregistered renderer breaks that invariant.

Source

Thrown at src/Components/Web.JS/src/Rendering/WebRendererInteropMethods.ts:45

    throw new Error(`Interop methods are already registered for renderer ${rendererId}`);
  }

  interopMethodsByRenderer.set(rendererId, interopMethods);

  if (jsComponentParameters && jsComponentInitializers && Object.keys(jsComponentParameters).length > 0) {
    const manager = getInteropMethods(rendererId);
    enableJSRootComponents(rendererId, manager, jsComponentParameters, jsComponentInitializers);
  }

  rendererByIdResolverMap.get(rendererId)?.[0]?.();

  invokeRendererAttachedListeners(rendererId);
}

export function detachWebRendererInterop(rendererId: number): DotNet.DotNetObject {
  const interopMethods = interopMethodsByRenderer.get(rendererId);
  if (!interopMethods) {
    throw new Error(`Interop methods are not registered for renderer ${rendererId}`);
  }

  interopMethodsByRenderer.delete(rendererId);
  return interopMethods;
}

export function isRendererAttached(browserRendererId: number): boolean {
  return interopMethodsByRenderer.has(browserRendererId);
}

export function registerRendererAttachedListener(listener: (browserRendererId: number) => void) {
  rendererAttachedListeners.push(listener);
}

function invokeRendererAttachedListeners(browserRendererId: number) {
  for (const listener of rendererAttachedListeners) {
    listener(browserRendererId);
  }

View on GitHub (pinned to 294cab2f9b)

Solutions

  1. Guard the detach call with isRendererAttached(rendererId) before invoking detachWebRendererInterop.
  2. Ensure detach is called exactly once per attach by tracking attach/detach pairing in your own state machine.
  3. If re-initializing Blazor in a SPA without a full reload, perform a full location.reload() instead of partial teardown, since these maps are module-level singletons.
  4. Search your codebase for all detachWebRendererInterop call sites and confirm each has a matching attach.

Example fix

// before
const interop = detachWebRendererInterop(rendererId);

// after
if (isRendererAttached(rendererId)) {
  const interop = detachWebRendererInterop(rendererId);
  interop.dispose();
}
Defensive patterns

Strategy: validation

Validate before calling

import { isRendererAttached } from './WebRendererInteropMethods';

function safeDetach(rendererId: number) {
  if (!isRendererAttached(rendererId)) {
    return null; // nothing to detach
  }
  return detachWebRendererInterop(rendererId);
}

Type guard

function isDetachable(rendererId: number): rendererId is number {
  return isRendererAttached(rendererId);
}

Try / catch

try {
  const interop = detachWebRendererInterop(rendererId);
  interop.dispose();
} catch (e) {
  if (/not registered for renderer/.test((e as Error).message)) {
    // already detached or never attached — idempotent, ignore
  } else { throw e; }
}

Prevention

When it happens

Trigger: Calling detachWebRendererInterop(rendererId) for a rendererId that was never passed to attachWebRendererInterop, or calling it a second time after a successful detach (the map entry was deleted on the first detach).

Common situations: Manual teardown/disposal logic that double-disposes a renderer; a custom host that tears down renderers out of order; a circuit-handler that calls detach during both normal disconnect and app shutdown; test harnesses that re-initialize Blazor without resetting module-level state.

Related errors


AI-assisted analysis of dotnet/aspnetcore@294cab2f9b (2026-08-06). Data as JSON: /api/errors/baedc461d3927a6d. Report an issue: GitHub.