Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions ChartJs.Blazor.Tests/ChartJs.Blazor.Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>

<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.analyzers" Version="0.10.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="xunit" Version="2.7.1" />
<PackageReference Include="xunit.analyzers" Version="1.7.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="1.0.1" />
<PackageReference Include="coverlet.collector" Version="6.0.0">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
Expand Down
6 changes: 3 additions & 3 deletions global.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"sdk": {
"version": "3.1.300",
"version": "9.0.100",
"rollForward": "latestFeature",
"allowPrerelease": false
"allowPrerelease": true
}
}
}
77 changes: 67 additions & 10 deletions src/ChartJs.Blazor/Chart.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@ namespace ChartJs.Blazor
/// <summary>
/// Represents a Chart.js chart.
/// </summary>
public partial class Chart
public partial class Chart : IAsyncDisposable
{
private bool _chartInitialized;
private bool _renderQueued;

/// <summary>
/// This event is fired when the chart has been setup through interop and
/// the JavaScript chart object is available. Use this callback if you need to setup
Expand All @@ -24,13 +27,13 @@ public partial class Chart
/// Gets the injected <see cref="IJSRuntime"/> for the current Blazor application.
/// </summary>
[Inject]
protected IJSRuntime JsRuntime { get; set; }
protected IJSRuntime JsRuntime { get; set; } = default!;

/// <summary>
/// Gets or sets the configuration of the chart.
/// </summary>
[Parameter]
public ConfigBase Config { get; set; }
public ConfigBase Config { get; set; } = default!;

/// <summary>
/// Gets or sets the width of the canvas HTML element.
Expand All @@ -47,15 +50,13 @@ public partial class Chart
/// <inheritdoc />
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await JsRuntime.SetupChart(Config);
await SetupCompletedCallback.InvokeAsync(this);
}
else
if (!_chartInitialized)
{
await JsRuntime.UpdateChart(Config);
_chartInitialized = await TryInitializeChartAsync();
return;
}

await JsRuntime.UpdateChart(Config);
}

/// <summary>
Expand All @@ -66,7 +67,63 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
/// </summary>
public Task Update()
{
if (!_chartInitialized)
{
throw new InvalidOperationException("The chart has not been initialized yet.");
}

return JsRuntime.UpdateChart(Config).AsTask();
}

/// <inheritdoc />
public ValueTask DisposeAsync()
{
_chartInitialized = false;
return ValueTask.CompletedTask;
}

private async Task<bool> TryInitializeChartAsync()
{
try
{
await JsRuntime.SetupChart(Config);
await SetupCompletedCallback.InvokeAsync(this);
return true;
}
catch (JSDisconnectedException)
{
await QueueRenderAsync();
return false;
}
catch (InvalidOperationException ex) when (IsInteropUnavailable(ex))
{
await QueueRenderAsync();
return false;
}
}

private async Task QueueRenderAsync()
{
if (_renderQueued)
{
return;
}

_renderQueued = true;

await InvokeAsync(() =>
{
_renderQueued = false;
StateHasChanged();
});
Comment on lines +114 to +118

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Stop re-render loop when JS interop unavailable

When JsRuntime.SetupChart throws during prerendering or a disconnected circuit, TryInitializeChartAsync enqueues another render via QueueRenderAsync. The lambda clears _renderQueued before calling StateHasChanged, so every failed attempt immediately schedules the next render with no delay, and OnAfterRenderAsync hits the same failure again. In Blazor auto render this results in an unbounded render loop while JavaScript interop is unavailable, keeping the prerendered request alive and consuming CPU until the page becomes interactive (or indefinitely if it never does). The component should avoid calling StateHasChanged in this situation or throttle retries, because the framework will automatically re-render once interactivity is ready.

Useful? React with 👍 / 👎.

}

private static bool IsInteropUnavailable(InvalidOperationException exception)
{
var message = exception.Message;
return message.Contains("JavaScript interop calls cannot be issued", StringComparison.OrdinalIgnoreCase)
|| message.Contains("prerender", StringComparison.OrdinalIgnoreCase)
|| message.Contains("JSRuntime", StringComparison.OrdinalIgnoreCase);
}
}
}
17 changes: 8 additions & 9 deletions src/ChartJs.Blazor/ChartJs.Blazor.csproj
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>true</IsPackable>
<OutputType>Library</OutputType>
<LangVersion>default</LangVersion>
<RazorLangVersion>3.0</RazorLangVersion>
<LangVersion>latest</LangVersion>
<Title>ChartJs.Blazor</Title>
<RequireLicenseAcceptance>false</RequireLicenseAcceptance>
<Authors>Marius Muntean,Joelius300</Authors>
Expand Down Expand Up @@ -40,13 +41,11 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="3.1.8" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="Microsoft.TypeScript.MSBuild" Version="4.0.3">
<PackageReference Include="Microsoft.TypeScript.MSBuild" Version="5.4.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>

<ItemGroup>
Expand All @@ -62,7 +61,7 @@

<PropertyGroup>
<TypeScriptOutFile>wwwroot\ChartJsBlazorInterop.js</TypeScriptOutFile>
<TypeScriptTarget>ES2018</TypeScriptTarget>
<TypeScriptTarget>ES2020</TypeScriptTarget>
<TypeScriptAlwaysStrict>true</TypeScriptAlwaysStrict>
<TypeScriptInclude>Interop\TypeScript\*.ts</TypeScriptInclude>
</PropertyGroup>
Expand Down