[Blazor] Warn when performing JS interop outside of `OnAfterRender(Async)()` without checking `RendererInfo.IsInteractive`
- 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
Assessment
This issue has not been assessed yet.