Skip to content

[Bug]: Chart renderering issues on Blazor Server with slow connections #6607

Description

@njannink

Blazorise Version

2.1

What Blazorise provider are you running on?

Material

Link to minimal reproduction or a simple code snippet

I'm having issues with the Blazorise Charts when running on a bit slower connection on Blazor server. I cannot reproduce the issue consistently, but the code below seemed to have fixed this issue which seems to come from the slow loading of the js module or other imported files. Maybe there is any suggestion howto fix this issue, or possible its a bug on Blazorise side. The fix was generated by claude and assumes that when rendering AddLabelsDatasetsAndUpdate returns almost instantly it means that the js modules where not loaded yet so an additional refresh is fired after a delay.

public sealed class ChartBinding<TData>
{
    // If the JS-interop push returns faster than this, Blazorise's JSModule wasn't initialized yet and
    // the call silently no-oped. Don't advance the rendered sentinel; instead schedule another render
    // so we can retry once the chart child has finished its own OnAfterRenderAsync.
    private const int NoOpThresholdMs = 5;
    private const int RetryDelayMs = 50;

    private readonly Func<LineChart<int>> m_getChart;
    private readonly Func<Task<IEnumerable<TData>>> m_fetchData;
    private readonly Func<IReadOnlyList<TData>, (IReadOnlyList<string> labels, LineChartDataset<int>[] datasets)> m_build;
    private readonly Func<Task> m_requestRerender;

    private bool m_isRendering;
    private bool m_renderedAtLeastOnce;
    private object m_lastKey;

    public ChartBinding(
        Func<LineChart<int>> getChart,
        Func<Task<IEnumerable<TData>>> fetchData,
        Func<IReadOnlyList<TData>, (IReadOnlyList<string> labels, LineChartDataset<int>[] datasets)> build,
        Func<Task> requestRerender)
    {
        m_getChart = getChart;
        m_fetchData = fetchData;
        m_build = build;
        m_requestRerender = requestRerender;
    }

    /// <summary>
    /// Refreshes the chart if needed. Pass a <paramref name="key"/> that changes when underlying data
    /// should be re-fetched (e.g. a slider value). Pass a constant for "render once and stick".
    /// </summary>
    public async Task RenderAsync(object key = default)
    {
        key ??= "once";

        if (m_isRendering) return;
        if (m_renderedAtLeastOnce && Equals(m_lastKey, key)) return;

        var chart = m_getChart();
        if (chart is null) return;

        m_isRendering = true;
        try
        {
            var data = (await m_fetchData())?.ToList() ?? [];
            var (labels, datasets) = m_build(data);

            if (m_renderedAtLeastOnce)
            {
                await chart.Clear();
            }

            var start = DateTime.UtcNow;
            await chart.AddLabelsDatasetsAndUpdate(labels, datasets);

            if ((DateTime.UtcNow - start).TotalMilliseconds < NoOpThresholdMs)
            {
                // JSModule wasn't initialized yet — the push didn't reach JS. Schedule another render.
                _ = Task.Delay(RetryDelayMs).ContinueWith(_ => m_requestRerender(), TaskScheduler.Default);
                return;
            }

            m_lastKey = key;
            m_renderedAtLeastOnce = true;
        }
        finally
        {
            m_isRendering = false;
        }
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Fields

    No fields configured for Bug.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions