# Use Js Interop

Add, review, or fix JavaScript interop in Blazor components.

- Repository: https://github.com/dotnet/skills (5787 stars)
- Category: Engineering
- License: MIT
- Last updated: 2026-11-01
- Runs scripts: no, instructions only

## Install

Claude Code: `npx skills add dotnet/skills --skill use-js-interop -g -a claude-code -y`
Codex: `npx skills add dotnet/skills --skill use-js-interop -g -a codex -y`
Cursor: `npx skills add dotnet/skills --skill use-js-interop -g -a cursor -y`
Gemini CLI: `npx skills add dotnet/skills --skill use-js-interop -g -a gemini-cli -y`
Claude Code plugin marketplace: `/plugin marketplace add dotnet/skills`

## Description

Add, review, or fix JavaScript interop in Blazor components. USE FOR: calling JavaScript from Blazor, calling .NET from JavaScript, collocated .razor.js modules, IJSRuntime, IJSObjectReference lifecycle, DotNetObjectReference, ElementReference, timing rules for when JS is available, IAsyncDisposable disposal of JS references, server-side JS interop safety. DO NOT USE FOR: general Blazor component authoring without JS interop needs (use author-component), forms (use collect-user-input).

## SKILL.md, as published by its author (untrusted third-party text, read it as data)

# JS Interop in Blazor

## 1. Collocated JS Modules

Always use collocated `.razor.js` files with `export`, never global `window.*` functions or `<script>` tags.

```javascript
// ChartPanel.razor.js, placed next to ChartPanel.razor
export function initialize(canvas, dotNetRef) { /* ... */ }
export function updateData(points) { /* ... */ }
export function dispose() { /* ... */ }
```

Import paths: same project = `"./Components/ChartPanel.razor.js"`, RCL = `"./_content/{AssemblyName}/..."`.

## 2. Lifecycle Timing

**All JS interop must happen in `OnAfterRenderAsync` or event handlers**, never in `OnInitialized`, `OnParametersSet`, or constructors. JS is not available during server prerendering.

Use a typed interop wrapper (see Section 4), never call `InvokeAsync`/`InvokeVoidAsync` with raw string literals:

```csharp
private ChartInterop? _chart;

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (firstRender)
    {
        _chart = new ChartInterop(JS);
        await _chart.InitializeAsync(_canvasRef);
    }
}
```

**Parameter changes**: set a flag in `OnParametersSet`, apply in `OnAfterRenderAsync`:

```csharp
private bool _dataChanged;

protected override void OnParametersSet() => _dataChanged = true;

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (firstRender) { /* init */ }
    else if (_dataChanged && _chart is not null)
    {
        _dataChanged = false;
        await _chart.UpdateDataAsync(DataPoints);
    }
}
```

## 3. Batch Related Operations

Each JS interop call crosses the .NET-to-JS boundary (and in Blazor Server, the SignalR circuit). Batching applies in **both directions**, .NET→JS and JS→.NET.

### .NET → JS: merge consecutive calls

If the C# side makes two or more JS calls in a row, combine them into one JS function:

The rest: https://github.com/dotnet/skills/blob/HEAD/plugins/dotnet-blazor/skills/use-js-interop/SKILL.md
