blazorblazor-webassemblyfluent-ui.net-9.0

Pagination is not working properly in Blazor FluentDataGrid


I am trying to display data using FluentDataGrid but I am getting very strange behavior. Issues:

  1. The total item count of the grid is not updated properly. That is why pagination buttons are not working.
  2. I somehow manage to set the total items count, but then when I change the page, the event always gets 0 as page number.

Here is my code. I am using @rendermode = InteractiveAssembly.

@rendermode InteractiveWebAssembly

@inject HttpClientService httpClientService

<FluentDataGrid Items="RolesData?.Data?.AsQueryable()"
                TGridItem="GetRoles.Response"
                Pagination="paginationState"
                ShowHover="true"
                MultiLine="true"
                Loading="@gridLoader">
    <PropertyColumn Property="@(c => c.Name)" Sortable="false" />
    <TemplateColumn Title="Permissions">
        <p>@context.PermissionCount permissions assigned</p>
    </TemplateColumn>
    <TemplateColumn Title="Assigned To">
        <p>@context.AssignedTo uers</p>
    </TemplateColumn>
    <TemplateColumn Title="Actions">
        @if (context.IsPrimary == false)
        {
            <FluentButton aria-label="Edit item" IconEnd="@(new Icons.Regular.Size16.Edit())" OnClick="() => OnEditClick(context.Id)" />
            <FluentButton aria-label="Delete item" IconEnd="@(new Icons.Regular.Size16.Delete())" OnClick="() => OnDeleteClick(context.Id)" />
        }
    </TemplateColumn>
</FluentDataGrid>
<FluentPaginator State="@paginationState" CurrentPageIndexChanged="OnCurrentPageIndexChanged" />

That is my component. I am fetching data on OnAfterRenderAsync initially

private GetRoles.Request request = new GetRoles.Request();
private Pagination.Response<IEnumerable<GetRoles.Response>>? RolesData;

PaginationState paginationState = new PaginationState { ItemsPerPage = 10 };
bool gridLoader;

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

private async Task GetRoles()
{
    gridLoader = true;
    StateHasChanged();

    request.Page = paginationState.CurrentPageIndex;
    request.PageSize = paginationState.ItemsPerPage;

    var roleDataResponse = await httpClientService.GetAsync<Pagination.Response<IEnumerable<GetRoles.Response>>>("/api/roles", request);
    if (roleDataResponse != null)
    {
        RolesData = roleDataResponse;

        await paginationState.SetTotalItemCountAsync(roleDataResponse.TotalRecords);
    }

    gridLoader = false;
    StateHasChanged();

    Console.WriteLine($"CurrentPageIndex: {paginationState.CurrentPageIndex}");
    Console.WriteLine($"LastPageIndex: {paginationState.LastPageIndex}");
    Console.WriteLine($"TotalItemCount: {paginationState.TotalItemCount}");
}

The event which is handling the button click on the pagination is

async void OnCurrentPageIndexChanged(int index)
{
    await paginationState.SetCurrentPageIndexAsync(index);
    await GetRoles();
}

This is the data returned by the API

{
    "totalRecords": 11,
    "data": [
        {
            "id": "Dy9dkGzW",
            "name": "Test 9",
            "permissionCount": 2,
            "assignedTo": 0,
            "isPrimary": false
        },
        {
            "id": "eV9ablPg",
            "name": "Test 8",
            "permissionCount": 2,
            "assignedTo": 0,
            "isPrimary": false
        },
        {
            "id": "X892olBV",
            "name": "Test 7",
            "permissionCount": 10,
            "assignedTo": 0,
            "isPrimary": false
        },
        {
            "id": "8e7V896p",
            "name": "Test 6",
            "permissionCount": 1,
            "assignedTo": 0,
            "isPrimary": false
        },
        {
            "id": "1VGx2l5Q",
            "name": "Test 5",
            "permissionCount": 4,
            "assignedTo": 0,
            "isPrimary": false
        },
        {
            "id": "5Y7Qq7Bk",
            "name": "Test 4",
            "permissionCount": 9,
            "assignedTo": 0,
            "isPrimary": false
        },
        {
            "id": "O8l1wGqx",
            "name": "Test 3",
            "permissionCount": 2,
            "assignedTo": 0,
            "isPrimary": false
        },
        {
            "id": "Y8Gb39Bq",
            "name": "Test 2",
            "permissionCount": 3,
            "assignedTo": 0,
            "isPrimary": false
        },
        {
            "id": "QY7BV7OV",
            "name": "Test 1",
            "permissionCount": 2,
            "assignedTo": 0,
            "isPrimary": false
        },
        {
            "id": "ZYG8o71v",
            "name": "Finance",
            "permissionCount": 8,
            "assignedTo": 0,
            "isPrimary": false
        }
    ]
}

As you can see the 'totalRecords' are 11 here. The pagination should show total records as 11 and it should have 2 pages, but it is showing me this enter image description here

If I move 'await paginationState.SetTotalItemCountAsync(roleDataResponse.TotalRecords);' to the end of the method, it sets the total items count correctly and the pagination buttons are not disabled, but when I click on the next button, the event handler get 0 in 'index'.

Am I missing something here or doing something really wrong? Any help will be appreciated.

Thank you in advance.


Solution

  • You're trying to plumb this up the wrong way. You need to use the ItemsProvider, and then everything gets much simpler.

    As I'm not sure of your data object [and not about to try and re-invent it], I've simplified everything by substituting in the good old WeatherForecast.

    Here's the page code:

    @page "/"
    
    <PageTitle>Home</PageTitle>
    
    <h1>Hello, world!</h1>
    
    Welcome to your new Fluent Blazor app.
    <FluentDataGrid TGridItem="WeatherForecast"
                    Pagination="_paginationState"
                    ShowHover="true"
                    MultiLine="true"
                    ItemsProvider="GetDataAsync">
    
        <PropertyColumn Title="Date" Property="@(c => c!.Date)" Sortable="true" Align=Align.Start />
        <PropertyColumn Title="Temp. (C)" Property="@(c => c!.TemperatureC)" Sortable="true" Align=Align.Center />
        <PropertyColumn Title="Temp. (F)" Property="@(c => c!.TemperatureF)" Sortable="true" Align=Align.Center />
        <PropertyColumn Title="Summary" Property="@(c => c!.Summary)" Sortable="true" Align=Align.End />
    
    </FluentDataGrid>
    
    <FluentPaginator State="@_paginationState" />
    
    @code {
        private PaginationState _paginationState = new PaginationState { ItemsPerPage = 10 };
    
        private async ValueTask<GridItemsProviderResult<WeatherForecast>> GetDataAsync(GridItemsProviderRequest<WeatherForecast> request)
        {
            // Map the UI GridItemsProviderRequest to the data pipeline list request object
            var listRequest = new ListRequest(request.StartIndex, request.Count ?? _paginationState.ItemsPerPage);
    
            var result = await WeatherForecastProvider.GetAPIDataAsync(listRequest);
    
            // Map the data pipeline result to the UI GridItemsProviderResult
            return GridItemsProviderResult.From<WeatherForecast>(result.Items.ToList(), result.TotalRecords);
        }
    }
    

    And the supporting objects:

        // Stub Class to mimic getting data from an external source
        public static class WeatherForecastProvider
        {
            private static List<WeatherForecast> _weatherForecasts = new();
    
            public static async ValueTask<ListResult<WeatherForecast>> GetAPIDataAsync(ListRequest request)
            {
                // Fake as asynbc data get
                await Task.Yield();
    
                _weatherForecasts = _weatherForecasts ?? LoadData();
    
                var list = _weatherForecasts
                    .Skip(request.StartIndex)
                    .Take(request.PageSize)
                    .ToList();
    
                return new ListResult<WeatherForecast>(list, _weatherForecasts.Count());
            }
    
            private static List<WeatherForecast> LoadData()
            {
                var startDate = DateOnly.FromDateTime(DateTime.Now);
                var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" };
                return Enumerable.Range(1, 50).Select(index => new WeatherForecast
                    {
                        Date = startDate.AddDays(index),
                        TemperatureC = Random.Shared.Next(-20, 55),
                        Summary = summaries[Random.Shared.Next(summaries.Length)]
                    }).ToList();
            }
        }
    
        // The data pipeline request and result objects
        public readonly record struct ListRequest(int StartIndex, int PageSize);
        public readonly record struct ListResult<TRecord>(IEnumerable<TRecord> Items, int TotalRecords);
    
        public class WeatherForecast
        {
            public DateOnly Date { get; set; }
            public int TemperatureC { get; set; }
            public string? Summary { get; set; }
            public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
        }