DataGrid settings are not applied after 4.25.12

I have noticed that after updating the Razer Components to 4.25.12 (or newer), the saved grid settings are not applied any more. Reverting back to 4.25.11 fixes the issue.
My code is very similar to the example in the docs Blazor DataGrid Component - Save / Load Settings with LoadData | Free UI Components by Radzen.

I appreciate that the sample works ok. However I cannot figure why settings are not applied any more when using 4.25.12 version whilst it works ok on previous versions.

Looking at the change log I can see the following commit as a potential reason: DataGrid save/load settings will handle composite columns as well · radzenhq/radzen-blazor@e9f74bf · GitHub. It must be that in my case somehow the settingsChanged stays false thus leading to the parameter not being set.

Any help would be much appreciated. Thank you.

This is not the latest version.

I understand that. I am highlighting the version from which I can see a difference in behaviour. 4.25.12 and in any newer it manifests the behaviour I describe.

Let us know how to reproduce this. You can use our demos for reference.

Having done some experiments with my code, it seems that the issue manifests when the DataGrid is hosted within a component which is then hosted in a page. When I place the grid directly in a page it seems to be working OK.

I cannot verify for sure, but this line of code in the DataGrid's codebase added in 4.25.12 seems to be always FASLE when the DataGrid is placed inside another component

case nameof(Settings):
              settingsChanged = HasChanged(parameter.Value, Settings);

Both demos are exactly such setup:

Thank you for you quick replies @enchev.
It is helpful to know that the set up is similar.
As I do not know the reason why this stops working from 4.25.12 onwards, I am trying to identify potential causes. Apologies I cannot be more useful.

One more difference is that in my case I pass the data source from the page into the component that hosts the DataGrid


<CustomDataGrid DataSource=@Data>
@code{
 IEnumerable<> Data;
protected override async Task OnInitializedAsync()
{
    Data = await GenerateSomeData();
}


and inside the custom component:

<DataGrid Data="@_data">

[Parameter]
public IEnumerable<> DataSource { get; set; }

protected override void OnParametersSet()
{
    base.OnParametersSet();
    if (!_data.Any())
        _data = DataSource;
}

The data show OK in the grid and all functions of the grid work as normal. It is only the Settings that are not applied and the code I have to handle the settings is literally identical to the samples you have here: Blazor DataGrid Component - Save / Load Settings with LoadData | Free UI Components by Radzen

You can try to debug your application to check why the settings are reported as unchanged in your case.

I am not sure how to do what you recommend.
I load the page, I add/remove some columns, I change the page size and then I refresh the page using the browser's refresh button.
What I see is that the settings are read from the store ("window.localStorage.getItem") correctly as per my changes before I hit the refresh in the browser. However the settings are not reflected on the grid after the page is fully loaded.
To reiterate: this worked ok in 4.25.11

You can attach Radzen.Blazor.csproj to your application instead the nuget package.

I managed to understand a bit more by debugging the way suggested by @enchev. My solution now works OK but I cannot claim I have fixed it. What I have done it seems like I managed to bypass the issue. However I cannot claim it is a bug as I am not yet very confident with my understanding around the page life cycle events especially when child components are involved.

In my previous implementation I had an OnInitializedAsync() on the page where I was fetching the data to bind to the DataGrid. The data were set into a private member which was used as the data source for the custom control that I had in the page that encapsulated the DataGrid. The mere assignment to the private member was not refreshing the DataGrid to show the data so I had a paced a DataGrid.Reload() command into the page's OnAfterRenderAsync()

This combination seems to stumble upon a weird synchronization issue in the lifecycles events where the DataGrid's OnAfterRenderAsync() was called before the DataGrid's SetParametersAsync() got a non null value in the Settings parameter. As the OnAfterRenderAsync() includes a call to the LoadSettings() and that is assigning the Settings internally, it resulted in a case where the SetParametersAsync() when eventually the Settings parameter was not null was comparing it with the already assigned Settings property leading to the settingsChanged always being resulting as false, leading to never executing the LoadSettingsInternal() method resulting to no settings being set on the DataGrid.

I believe this is a weakness inside the Settings management model/code of the control that is worth reviewing. Effectively the parameter for settings should never be provided to the SetParametersAsync() method AFTER the internal Settings property is already set by the internal OnAfterRenderAsync() method. The HasChanged() method only checks referential equality for Settings hence when both the incoming parameter and the Settings property are not null it will return true hence the properties wont be set on the DataGrid.

I solved the issue by moving the data fetching code from my page's OnInitializedAsync() to my page's SetParametersAsync(). Inside it I call the StateHasChanged() as well which allows the DataGrid to refresh. Because of that I could remove the Reload() method call I had inside the page's OnAfterRenderAsync() and all that lead to the problem going away.

I have the same issue after 4.25.12.

However I cannot try your workaround, as I am using LoadData for data binding. I'm going to attempt to make a reproducible example using their demo code

Same thing happened to us, once we update to anything after 4.25.11 all of our grids that used to work perfectly fine stop loading data on initial load. We also use the LoadData binding approach. I thought perhaps waiting a few versions would fix the issue, but even with the latest version grid load event is not fired. It seems to happen when using RadMenuPanel navigation, if I f5 on the page itself the load event is triggered...

Looks like it's related to how we load GridSettings and something must have changed there. Following the demo way to handle Save/Load settings using LoadData (Blazor DataGrid Component - Save / Load Settings with LoadData | Free UI Components by Radzen) seems to work.

Turns out that I was wrong and it just happened to work that one time. So this bothered me and I grabbed the project and included it to walk through. I've found that adding settingsChanged = true; on line 2357 of RadzenDataGrid.razor.cs fixes the issue reliably for me. Looking at it closer it makes sense that settingsChanged is marked as true when the settings are actually loaded and different, but would be great to have somebody look into this further.

Okay @enchev we have a few other commenters on this issue, and I have made you a reproducible example.

  • I modified your DataGridSaveSettingsLoadData demo page to include a DataFilter for filtering.
  • Initial settings are that only one column is visible (Photo).
  • The idea for this page, is that when you choose a column in the DataFilter, it should automatically changed the DataGrid column to Visible.

This worked up until version 4.25.11, but stopped working in version 4.25.12. I have it narrowed down to line 2364 in the RadzenDataGrid.razor.cs file:

Line 2364:

if (settings != null && settingsChanged)
{
    await LoadSettingsInternal(settings);
}

In 4.25.12, you added the && settingsChanged before calling LoadSettingsInternal. However, even though we have changed the Settings, by the time we get to line 2274 in the SetParametersAsync, both the Settings and the incoming ParameterView are already updated so it does not think anything is changed, hence settingsChanged here is false:

Line 2274:

case nameof(Settings):
    settingsChanged = HasChanged(parameter.Value, Settings); break;

I hope this is enough for you to go off of, let me know if I can explain anything better.

Full component code:

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

@inject IDbContextFactory<NorthwindContext> DbFactory
@inject IJSRuntime JSRuntime
@inject NavigationManager NavigationManager

@implements IDisposable

<div class="row mb-3">
    <div class="col-lg-8">
        <RadzenDataFilter @ref="dataFilter" Data="@employees" TItem="Employee"
                          ViewChanged=@(view => OnViewChange(view)) class="mt-2" AllowColumnFiltering="true">
            <Properties>
                <RadzenDataFilterProperty TItem="Employee" Property="FirstName" Title="First Name" />
                <RadzenDataFilterProperty TItem="Employee" Property="LastName" Title="Last Name" />
                <RadzenDataFilterProperty TItem="Employee" Property="Title" Title="Title" />
                <RadzenDataFilterProperty TItem="Employee" Property="EmployeeID" Title="Employee ID" />
                <RadzenDataFilterProperty TItem="Employee" Property="HireDate" Title="Hire Date" FormatString="{0:d}" />
                <RadzenDataFilterProperty TItem="Employee" Property="City" Title="City" />
                <RadzenDataFilterProperty TItem="Employee" Property="Country" Title="Country" />
            </Properties>
        </RadzenDataFilter>
    </div>
</div>

<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" 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="@filteredEmployees" IsLoading=@isLoading Count="@count" LoadData=@LoadData
                PageSize="@pageSize" PageSizeOptions="@pageSizeOptions" ShowPagingSummary="true" PageSizeChanged="@(args => pageSize = args)">
    <Columns>
        <RadzenDataGridColumn Property="Photo" Title="Employee" Sortable="false" Filterable="false">
            <Template Context="data">
                <RadzenImage Path="@data.Photo" style="width: 40px; height: 40px; border-radius: 8px; margin-right: 8px;" AlternateText="@(data.FirstName + " " + data.LastName)" />
            </Template>
        </RadzenDataGridColumn>
        <RadzenDataGridColumn TItem="Employee" Property="FirstName" Title="First Name" Visible="false" />
        <RadzenDataGridColumn TItem="Employee" Property="LastName" Title="Last Name" Visible="false" />
        <RadzenDataGridColumn TItem="Employee" Property="Title" Title="Title" Visible="false" />
        <RadzenDataGridColumn TItem="Employee" Property="EmployeeID" Title="Employee ID" Visible="false" />
        <RadzenDataGridColumn TItem="Employee" Property="HireDate" Title="Hire Date" FormatString="{0:d}" Visible="false" />
        <RadzenDataGridColumn TItem="Employee" Property="City" Title="City" Visible="false" />
        <RadzenDataGridColumn TItem="Employee" Property="Country" Title="Country" Visible="false" />
    </Columns>
</RadzenDataGrid>

<EventConsole @ref=@console />

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

    protected NorthwindContext dbContext;

    RadzenDataFilter<Employee> dataFilter;
    RadzenDataGrid<Employee> grid;

    IEnumerable<Employee> filteredEmployees;
    IEnumerable<Employee> employees;
    EventConsole console;

    int count;
    bool isLoading = false;

    protected override async Task OnInitializedAsync()
    {
        dbContext = DbFactory.CreateDbContext();
        await dbContext.SeedAsync();

        employees = Enumerable.Empty<Employee>();
    }

    async Task LoadData(LoadDataArgs args)
    {
        isLoading = true;

        await Task.Yield();

        var query = dbContext.Employees.AsQueryable();

        query = query.Where(dataFilter);

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

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

        count = query.Count();

        filteredEmployees = query.Skip(args.Skip.Value).Take(args.Top.Value).ToList();

        isLoading = false;
    }

    async Task OnViewChange(IQueryable<Employee> view)
    {
        if (dataFilter is not null)
        {
            // automatically display columns when are first chosen as a filter
            foreach (var filter in dataFilter.Filters)
            {
                if (filter.Property is not null)
                {
                    _settings.Columns.Where(c => c.Property == filter.Property).FirstOrDefault().Visible = true;
                }
            }
        }

        await grid.Reload();
    }

    // initial Default Settings, because before changing the grid _settings will be null
    static string defaultSettings = @"{""Columns"":[{""UniqueID"":null,""Property"":""Photo"",""Visible"":true,""Width"":null,""OrderIndex"":null,""SortOrder"":null,""SortIndex"":null,""FilterValue"":null,""FilterOperator"":6,""SecondFilterValue"":null,""SecondFilterOperator"":0,""LogicalFilterOperator"":0,""CustomFilterExpression"":null},{""UniqueID"":null,""Property"":""FirstName"",""Visible"":false,""Width"":null,""OrderIndex"":null,""SortOrder"":null,""SortIndex"":null,""FilterValue"":null,""FilterOperator"":6,""SecondFilterValue"":null,""SecondFilterOperator"":0,""LogicalFilterOperator"":0,""CustomFilterExpression"":null},{""UniqueID"":null,""Property"":""LastName"",""Visible"":false,""Width"":null,""OrderIndex"":null,""SortOrder"":null,""SortIndex"":null,""FilterValue"":null,""FilterOperator"":6,""SecondFilterValue"":null,""SecondFilterOperator"":0,""LogicalFilterOperator"":0,""CustomFilterExpression"":null},{""UniqueID"":null,""Property"":""Title"",""Visible"":false,""Width"":null,""OrderIndex"":null,""SortOrder"":null,""SortIndex"":null,""FilterValue"":null,""FilterOperator"":6,""SecondFilterValue"":null,""SecondFilterOperator"":0,""LogicalFilterOperator"":0,""CustomFilterExpression"":null},{""UniqueID"":null,""Property"":""EmployeeID"",""Visible"":false,""Width"":null,""OrderIndex"":null,""SortOrder"":null,""SortIndex"":null,""FilterValue"":null,""FilterOperator"":0,""SecondFilterValue"":null,""SecondFilterOperator"":0,""LogicalFilterOperator"":0,""CustomFilterExpression"":null},{""UniqueID"":null,""Property"":""HireDate"",""Visible"":false,""Width"":null,""OrderIndex"":null,""SortOrder"":null,""SortIndex"":null,""FilterValue"":null,""FilterOperator"":0,""SecondFilterValue"":null,""SecondFilterOperator"":0,""LogicalFilterOperator"":0,""CustomFilterExpression"":null},{""UniqueID"":null,""Property"":""City"",""Visible"":false,""Width"":null,""OrderIndex"":null,""SortOrder"":null,""SortIndex"":null,""FilterValue"":null,""FilterOperator"":6,""SecondFilterValue"":null,""SecondFilterOperator"":0,""LogicalFilterOperator"":0,""CustomFilterExpression"":null},{""UniqueID"":null,""Property"":""Country"",""Visible"":false,""Width"":null,""OrderIndex"":null,""SortOrder"":null,""SortIndex"":null,""FilterValue"":null,""FilterOperator"":6,""SecondFilterValue"":null,""SecondFilterOperator"":0,""LogicalFilterOperator"":0,""CustomFilterExpression"":null}],""Groups"":[],""CurrentPage"":0,""PageSize"":4}";

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

    private async Task LoadStateAsync()
    {
        console.Log("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();
            }
        }
    }

    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;
        }
    }

    public void Dispose()
    {
        dbContext?.Dispose();
    }
}

They never replied to this topic, but it looks like they implemented my fix in version v4.29.2 (Release 4.29.2 · radzenhq/radzen-blazor · GitHub)

We were able to grab the latest and everything works great for us now!

Interesting, as I would have assumed it was the same issue but I guess not.
I am working with the latest version of Radzen, and my example I provided is still not functioning.

Referring to the added line in 4.29.2:

settingsChanged = true;

This line never gets hit, as args.Settings does in fact equal settings, even though we have changed a setting. For some reason both are already updated by the time this event gets fired so it still doesn't think settings have Changed.

image

This code was submitted as pull request from one of our users - you can check the history in our repo.

That’s correct, after your post I have added the proposed fix however forgot to write here.

@nitrouscookies if you believe you have found a bug you can submit pull request with a fix.

I would like also to kindly remind that although we monitor closely our forum the support is guaranteed for paid subscribers only. Our components are free and open source under MIT license and everyone can change or adapt the code to serve specific needs.