DataGrid : Load data occured twice if load settings is async #1816

Closed
opened 2026-01-29 17:58:58 +00:00 by claunia · 5 comments
Owner

Originally created by @tale80 on GitHub (Jul 18, 2025).

Describe the bug
On a grid with LoadData event and LoadSettings async event (need to load settings from a database), LoadData occured before LoadSettings.
The grid load data initialy without settings, and then reload after settings are loaded

To Reproduce
Steps to reproduce the behavior:

  1. Go to 'Save Settings sample'
  2. Apply a filter on the grid. For example, filter on "Sales representative" in title
  3. Go to "Edit source" and back to "Example" to create a new initial load
  4. See error in console, 1 "LoadStateAsync" and 2 "LoadData"
Image

This error seems to happen because LoadSettings is not awaited before to start LoadData

Expected behavior
Wait settings before to load data to prevent multiple grid loading

Desktop (please complete the following information):

  • Browser : Chrome
  • Radzen Version : 7.1.5
Originally created by @tale80 on GitHub (Jul 18, 2025). **Describe the bug** On a grid with LoadData event and LoadSettings async event (need to load settings from a database), LoadData occured before LoadSettings. The grid load data initialy without settings, and then reload after settings are loaded **To Reproduce** Steps to reproduce the behavior: 1. Go to '[Save Settings sample](https://blazor.radzen.com/datagrid-save-settings-loaddata?theme=material3)' 2. Apply a filter on the grid. For example, filter on "Sales representative" in title 3. Go to "Edit source" and back to "Example" to create a new initial load 4. See error in console, 1 "LoadStateAsync" and 2 "LoadData" <img width="1472" height="841" alt="Image" src="https://github.com/user-attachments/assets/d3ca2195-0ab1-44f1-a945-625333dfcaf3" /> This error seems to happen because LoadSettings is not awaited before to start LoadData **Expected behavior** Wait settings before to load data to prevent multiple grid loading **Desktop (please complete the following information):** - Browser : Chrome - Radzen Version : 7.1.5
Author
Owner

@tale80 commented on GitHub (Jul 18, 2025):

if needed, replace the demo sample by this code, it adds Start/End console log on events to see when events start and finish

@using Radzen
@using RadzenBlazorDemos.Data
@using RadzenBlazorDemos.Models.Northwind
@using Microsoft.EntityFrameworkCore
@using RadzenBlazorDemos.Services
@using Microsoft.JSInterop
@using System.Text.Json

@inject IJSRuntime JSRuntime
@inject NavigationManager NavigationManager

@inherits DbContextPage

<p>This example shows how to save/load DataGrid state using Settings property when binding using LoadData event.</p>
<p>The state includes current page index, page size, groups and columns filter, sort, order, width and visibility.</p>
<RadzenButton Click="@(args => Settings = null)" Text="Clear saved settings" Style="margin-bottom: 16px" />
<RadzenButton Click="@(args => NavigationManager.NavigateTo("/datagrid-save-settings-loaddata", true))" Text="Reload" Style="margin-bottom: 16px" />
<RadzenDataGrid @ref=grid @bind-Settings="@Settings" LoadSettings="@LoadSettings" AllowFiltering="true" AllowColumnPicking="true" AllowGrouping="true" AllowPaging="true" 
                AllowSorting="true" AllowMultiColumnSorting="true" ShowMultiColumnSortingIndex="true"
                AllowColumnResize="true" AllowColumnReorder="true" ColumnWidth="200px"
                FilterPopupRenderMode="PopupRenderMode.OnDemand" FilterCaseSensitivity="FilterCaseSensitivity.CaseInsensitive"
                Data="@employees" IsLoading=@isLoading Count="@count" LoadData=@LoadData
                PageSize="@pageSize" PageSizeOptions="@pageSizeOptions" ShowPagingSummary="true" PageSizeChanged="@(args => pageSize = args)">
    <Columns>
        <RadzenDataGridColumn Property="@nameof(Employee.Photo)" Title="Employee" Sortable="false" Filterable="false">
            <Template Context="data">
                <RadzenImage Path="@data.Photo" Style="width: 40px; height: 40px;" class="rz-border-radius-2 rz-me-2" AlternateText="@(data.FirstName + " " + data.LastName)" />
                @data.FirstName @data.LastName
            </Template>
        </RadzenDataGridColumn>
        <RadzenDataGridColumn Property="@nameof(Employee.Title)" Title="Title" />
        <RadzenDataGridColumn Property="@nameof(Employee.EmployeeID)" Title="Employee ID" />
        <RadzenDataGridColumn Property="@nameof(Employee.HireDate)" Title="Hire Date" FormatString="{0:d}" />
        <RadzenDataGridColumn Property="@nameof(Employee.City)" Title="City" />
        <RadzenDataGridColumn Property="@nameof(Employee.Country)" Title="Country" />
    </Columns>
</RadzenDataGrid>

<EventConsole @ref=@console />

@code {
    IEnumerable<int> pageSizeOptions = new int[] { 4, 6, 8 };
    int pageSize = 4;

    RadzenDataGrid<Employee> grid;
    IEnumerable<Employee> employees;
    EventConsole console;

    int count;
    bool isLoading = false;
    async Task LoadData(LoadDataArgs args)
    {
        console.Log("Start LoadData");
        isLoading = true;

        await Task.Yield();

        var query = dbContext.Employees.AsQueryable();

        if (!string.IsNullOrEmpty(args.Filter))
        {
            query = query.Where(grid.ColumnsCollection);
        }

        if (!string.IsNullOrEmpty(args.OrderBy))
        {
            query = query.OrderBy(args.OrderBy);
        }

        count = query.Count();

        // Simulate async data loading
        await Task.Delay(2000);

        employees = await Task.FromResult(query.Skip(args.Skip.Value).Take(args.Top.Value).ToList());

        isLoading = false;
        console.Log("End LoadData");
    }

    DataGridSettings _settings;
    public DataGridSettings Settings
    {
        get
        {
            return _settings;
        }
        set
        {
            if (_settings != value)
            {
                _settings = value;
                console.Log("Set");
                InvokeAsync(SaveStateAsync);
            }
        }
    }

    private async Task LoadStateAsync()
    {
        console.Log("Start LoadStateAsync");
        var result = await JSRuntime.InvokeAsync<string>("window.localStorage.getItem", "SettingsLoadData");
        if (!string.IsNullOrEmpty(result) && result != "null")
        {
            _settings = JsonSerializer.Deserialize<DataGridSettings>(result);
            if (_settings.PageSize.HasValue)
            {
                pageSize = _settings.PageSize.Value;
                await Task.Yield();
            }
        }
        console.Log("End LoadStateAsync");
    }

    private async Task SaveStateAsync()
    {
        console.Log("SaveStateAsync");
        await JSRuntime.InvokeVoidAsync("window.localStorage.setItem", "SettingsLoadData", JsonSerializer.Serialize<DataGridSettings>(Settings));
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await LoadStateAsync();
        }
    }

    void LoadSettings(DataGridLoadSettingsEventArgs args)
    {
        if (Settings != null)
        {
            args.Settings = Settings;
        }
    }
}

Result :
Console log

Clear console
11:55:09.34 Start LoadStateAsync
11:55:09.34 Start LoadData
11:55:09.37 End LoadStateAsync
11:55:11.37 End LoadData
11:55:11.39 Start LoadData
11:55:13.43 End LoadData

@tale80 commented on GitHub (Jul 18, 2025): if needed, replace the demo sample by this code, it adds Start/End console log on events to see when events start and finish ```csharp @using Radzen @using RadzenBlazorDemos.Data @using RadzenBlazorDemos.Models.Northwind @using Microsoft.EntityFrameworkCore @using RadzenBlazorDemos.Services @using Microsoft.JSInterop @using System.Text.Json @inject IJSRuntime JSRuntime @inject NavigationManager NavigationManager @inherits DbContextPage <p>This example shows how to save/load DataGrid state using Settings property when binding using LoadData event.</p> <p>The state includes current page index, page size, groups and columns filter, sort, order, width and visibility.</p> <RadzenButton Click="@(args => Settings = null)" Text="Clear saved settings" Style="margin-bottom: 16px" /> <RadzenButton Click="@(args => NavigationManager.NavigateTo("/datagrid-save-settings-loaddata", true))" Text="Reload" Style="margin-bottom: 16px" /> <RadzenDataGrid @ref=grid @bind-Settings="@Settings" LoadSettings="@LoadSettings" AllowFiltering="true" AllowColumnPicking="true" AllowGrouping="true" AllowPaging="true" AllowSorting="true" AllowMultiColumnSorting="true" ShowMultiColumnSortingIndex="true" AllowColumnResize="true" AllowColumnReorder="true" ColumnWidth="200px" FilterPopupRenderMode="PopupRenderMode.OnDemand" FilterCaseSensitivity="FilterCaseSensitivity.CaseInsensitive" Data="@employees" IsLoading=@isLoading Count="@count" LoadData=@LoadData PageSize="@pageSize" PageSizeOptions="@pageSizeOptions" ShowPagingSummary="true" PageSizeChanged="@(args => pageSize = args)"> <Columns> <RadzenDataGridColumn Property="@nameof(Employee.Photo)" Title="Employee" Sortable="false" Filterable="false"> <Template Context="data"> <RadzenImage Path="@data.Photo" Style="width: 40px; height: 40px;" class="rz-border-radius-2 rz-me-2" AlternateText="@(data.FirstName + " " + data.LastName)" /> @data.FirstName @data.LastName </Template> </RadzenDataGridColumn> <RadzenDataGridColumn Property="@nameof(Employee.Title)" Title="Title" /> <RadzenDataGridColumn Property="@nameof(Employee.EmployeeID)" Title="Employee ID" /> <RadzenDataGridColumn Property="@nameof(Employee.HireDate)" Title="Hire Date" FormatString="{0:d}" /> <RadzenDataGridColumn Property="@nameof(Employee.City)" Title="City" /> <RadzenDataGridColumn Property="@nameof(Employee.Country)" Title="Country" /> </Columns> </RadzenDataGrid> <EventConsole @ref=@console /> @code { IEnumerable<int> pageSizeOptions = new int[] { 4, 6, 8 }; int pageSize = 4; RadzenDataGrid<Employee> grid; IEnumerable<Employee> employees; EventConsole console; int count; bool isLoading = false; async Task LoadData(LoadDataArgs args) { console.Log("Start LoadData"); isLoading = true; await Task.Yield(); var query = dbContext.Employees.AsQueryable(); if (!string.IsNullOrEmpty(args.Filter)) { query = query.Where(grid.ColumnsCollection); } if (!string.IsNullOrEmpty(args.OrderBy)) { query = query.OrderBy(args.OrderBy); } count = query.Count(); // Simulate async data loading await Task.Delay(2000); employees = await Task.FromResult(query.Skip(args.Skip.Value).Take(args.Top.Value).ToList()); isLoading = false; console.Log("End LoadData"); } DataGridSettings _settings; public DataGridSettings Settings { get { return _settings; } set { if (_settings != value) { _settings = value; console.Log("Set"); InvokeAsync(SaveStateAsync); } } } private async Task LoadStateAsync() { console.Log("Start LoadStateAsync"); var result = await JSRuntime.InvokeAsync<string>("window.localStorage.getItem", "SettingsLoadData"); if (!string.IsNullOrEmpty(result) && result != "null") { _settings = JsonSerializer.Deserialize<DataGridSettings>(result); if (_settings.PageSize.HasValue) { pageSize = _settings.PageSize.Value; await Task.Yield(); } } console.Log("End LoadStateAsync"); } private async Task SaveStateAsync() { console.Log("SaveStateAsync"); await JSRuntime.InvokeVoidAsync("window.localStorage.setItem", "SettingsLoadData", JsonSerializer.Serialize<DataGridSettings>(Settings)); } protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { await LoadStateAsync(); } } void LoadSettings(DataGridLoadSettingsEventArgs args) { if (Settings != null) { args.Settings = Settings; } } } ``` Result : Console log Clear console 11:55:09.34 Start LoadStateAsync 11:55:09.34 Start LoadData 11:55:09.37 End LoadStateAsync 11:55:11.37 End LoadData 11:55:11.39 Start LoadData 11:55:13.43 End LoadData
Author
Owner

@enchev commented on GitHub (Jul 18, 2025):

Yes, this is how loading settings works. The DataGrid should be bound to load settings and if the binding is late in the cycle you will get multiple calls.

@enchev commented on GitHub (Jul 18, 2025): Yes, this is how loading settings works. The DataGrid should be bound to load settings and if the binding is late in the cycle you will get multiple calls.
Author
Owner

@tale80 commented on GitHub (Jul 18, 2025):

@enchev : ok... strange behavior, waste resources, increase latency, and for UX user can see grid display all data then blink to display filtered data

@tale80 commented on GitHub (Jul 18, 2025): @enchev : ok... strange behavior, waste resources, increase latency, and for UX user can see grid display all data then blink to display filtered data
Author
Owner

@enchev commented on GitHub (Jul 18, 2025):

Feel free to submit better working version.

@enchev commented on GitHub (Jul 18, 2025): Feel free to submit better working version.
Author
Owner

@tale80 commented on GitHub (Jul 18, 2025):

@enchev : I will look if I can set a test environment of Radzen solution

By just reading the RadzenDataGrid Component, something like this should be a beginning :

        internal async Task InvokeLoadData(int start, int top)
        {
            if (LoadSettings != null)
            {
                var args = new Radzen.DataGridLoadSettingsEventArgs() { Settings = settings };

                if (LoadSettings != null)
                {
                    LoadSettings(args);
                }

                if (args.Settings != settings)
                {
                    settings = args.Settings;
                    settingsChanged = true;
                }
            }

            var orderBy = GetOrderBy();
.
.
.

this snippet was taken from OnAfterRenderAsync and can be refactored between the 2 methods I think, but maybe something better can be done.

Another option can be to change the LoadSettings Action to set it as an EventCallback, and ensure at beginning this callback is just called before LoadData. Sound as a better option but changing the type of LoadSettings can have some breaking change for others Radzen's users

Or maybe introduced a new EventCallback for settings to keep the existing action. Or create something like a BeforeLoadData to allow developers to do some preparation before to finalize the data grid initialization and call the LoadData

@tale80 commented on GitHub (Jul 18, 2025): @enchev : I will look if I can set a test environment of Radzen solution By just reading the RadzenDataGrid Component, something like this should be a beginning : ```csharp internal async Task InvokeLoadData(int start, int top) { if (LoadSettings != null) { var args = new Radzen.DataGridLoadSettingsEventArgs() { Settings = settings }; if (LoadSettings != null) { LoadSettings(args); } if (args.Settings != settings) { settings = args.Settings; settingsChanged = true; } } var orderBy = GetOrderBy(); . . . ``` this snippet was taken from `OnAfterRenderAsync` and can be refactored between the 2 methods I think, but maybe something better can be done. Another option can be to change the `LoadSettings` Action to set it as an EventCallback, and ensure at beginning this callback is just called before `LoadData`. Sound as a better option but changing the type of LoadSettings can have some breaking change for others Radzen's users Or maybe introduced a new EventCallback for settings to keep the existing action. Or create something like a `BeforeLoadData` to allow developers to do some preparation before to finalize the data grid initialization and call the LoadData
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/radzen-blazor#1816