dotnet / dotnet/aspnetcore

[Blazor] Warn when performing JS interop outside of `OnAfterRender(Async)()` without checking `RendererInfo.IsInteractive`

Open
#65,233 3 comments 3 reactions 1 assignee Claimed by @javiercn View on GitHub
analyzer area-blazor
Dominant language
C#
Stars
38.4k
Forks
10.9k
Avg merge
2d 5h
Merged PRs (30d)
276

Description

Warn when JS interop is used outside of `OnAfterRender(Async)` or inside methods that are called outside of `OnAfterRender(Async)` unless the method first checks `RenderHandle.RendererInfo.IsInteractive` to ensure that the context is interactive.

### Background

During prerendering, calling into JavaScript isn't possible because there's no live browser DOM connection. The `OnAfterRender{Async}` lifecycle events aren't called during prerendering - they only execute after the component renders interactively.

---

## ⚠️ Scenarios Where We Should Warn

### 1. JS Interop in `OnInitialized{Async}`

```csharp
@inject IJSRuntime JS

@code {
protected override async Task OnInitializedAsync()
{
// ⚠️ WARNING: JS interop called without checking RendererInfo.IsInteractive
await JS.InvokeVoidAsync("console.log", "This will fail during prerendering!");
}
}
```

### 2. JS Interop in `OnParametersSet{Async}`

```csharp
@inject IJSRuntime JS

@code {
protected override async Task OnParametersSetAsync()
{
// ⚠️ WARNING: JS interop called without checking RendererInfo.IsInteractive
await JS.InvokeAsync("processData", Data);
}
}
```

### 3. JS Interop in Methods Called from Non-Interactive Lifecycle Methods

```csharp
@inject IJSRuntime JS

@code {
protected override async Task OnInitializedAsync()
{
await LoadDataAsync();
}

private async Task LoadDataAsync()
{
// ⚠️ WARNING: JS interop called in method invoked from OnInitializedAsync
var data = await JS.InvokeAsync("fetchData");
}
}
```

### 4. JS Interop in Form Event Handlers (Static SSR)

```csharp
@inject IJSRuntime JS

...

@code {
private async Task HandleSubmit()
{
// ⚠️ WARNING: In static SSR, this handler is invoked server-side without interactivity
await JS.InvokeVoidAsync("showSuccessNotification");
}
}
```

---

## ✅ Correct Patterns (Should NOT Warn)

### 1. JS Interop in `OnAfterRender{Async}`

```csharp
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
// ✅ CORRECT: JS interop in OnAfterRenderAsync
await JS.InvokeAsync("scrollElementIntoView", divElement);
}
}
```

### 2. JS Interop Guarded by `RendererInfo.IsInteractive`

```csharp
protected override async Task OnInitializedAsync()
{
if (RendererInfo.IsInteractive)
{
// ✅ CORRECT: Guarded by IsInteractive check
await JS.InvokeVoidAsync("initializeComponent");
}
}
```

### 3. JS Interop in Event Handlers (Interactive Components)

```csharp
@rendermode InteractiveServer

@code {
private async Task OnButtonClick()
{
// ✅ Event handlers in interactive components are safe
await JS.InvokeVoidAsync("handleButtonClick");
}
}
```

---

## Implementation Considerations

- **Flow Analysis**: Detect when JS interop methods are reachable from non-interactive lifecycle methods without an `IsInteractive` guard
- **Method Call Tracking**: Track method calls transitively
- **Guard Pattern Recognition**: Recognize `if (RendererInfo.IsInteractive)` and early return patterns
- **Render Mode Awareness**: Consider the component's render mode when analyzing event handlers

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.