Blazor Virtualize component not rendering items on scroll

Viewed 1620

I'm trying to create a forum website using .Net 5 and blazor. To load a forum thread efficiently I'm trying to use the new Virtualize component. I have an issue though. It only loads the first 35 items, and then doesn't respond to scrolling, so I have a huge blank space, where items were suppose to load in.

The following is the page for the code.

Please don't mind that the LoadSvar method simply takes a range in a list, it's currently just for testing. It will call a service with a range in the end.

    @page "/Traade/{traadid}"
    @using ViewModels.Traade
    @using Entities.Traad
    @inject ISvarService SvarService
    @inject ICurrentUserService CurrentUserService
    @if (_loaded)
    {
        <div class="container">
            <div class="margin-top-10px">
                <div class="row">
                    <div class="col-xl-3 space-content">
                        <div class="margin-bottom-10px">
                            <ForumOversigt></ForumOversigt>
                        </div>
                        <div>
                            <KoebSalgOversigt></KoebSalgOversigt>
                        </div>
                    </div>
                    <div class="col-xl-6 space-content">
                        <div class="margin-bottom-10px">
                            <MainSvar Traad="@_listSvarViewModel.Traad"></MainSvar>
                        </div>
                        <Virtualize ItemsProvider="@LoadSvar">
                            <ItemContent>
                                <SvarItem Svar="@context"
                                          IsAuthenticated="@_isAuthenticated"
                                          IsAdmin="@_listSvarViewModel.IsAdmin">
                                </SvarItem>
                            </ItemContent>
                            <Placeholder>
                                <LoadingScreen></LoadingScreen>
                            </Placeholder>
                        </Virtualize>
                        @* @foreach (var svar in _listSvarViewModel.Svar) *@
                        @* { *@
                        @*     _svarCount++; *@
                        @*     if (svar.IsHidden && !_listSvarViewModel.IsAdmin) *@
                        @*     { *@
                        @*         continue; *@
                        @*     } *@
                        @*     <div class="margin-bottom-10px"> *@
                        @*         @if (_svarCount == _total) *@
                        @*         { *@
                        @*             <span id="seneste"></span> *@
                        @*         } *@
                        @*         <SvarItem Svar="@svar" SvarCount="@_svarCount" IsAuthenticated="@_isAuthenticated"></SvarItem> *@
                        @*     </div> *@
                        @* } *@
                        <CascadingValue Value="this">
                            @if (_isAuthenticated)
                            {
                                <span id="seneste"></span>
                                <CreateSvar Traad="@_listSvarViewModel.Traad"></CreateSvar>                        }
                            else
                            {
                                <span id="seneste"></span>
                                <CreateSvarGuest Traad="@_listSvarViewModel.Traad"></CreateSvarGuest>                        }
                        </CascadingValue>
                    </div>
                    <div class="col-xl-3 space-content">
                        <FrontPageSettings></FrontPageSettings>
                    </div>
                </div>
            </div>
        </div>
    }
    else
    {
        <LoadingScreen/>
    }
    
    @code {
    
        [CascadingParameter]
        private Task<AuthenticationState> AuthenticationStateTask { get; set; }
    
        [Parameter]
        public string TraadId { get; set; }
    
        bool _loaded;
        ListSvarViewModel _listSvarViewModel = new();
        bool _isAuthenticated;
        int _svarCount;
        int _total;
    
        public override Task SetParametersAsync(ParameterView parameters)
        {
            _loaded = false;
            return base.SetParametersAsync(parameters);
        }
    
        protected override async Task OnParametersSetAsync()
        {
        //Get is admin from userService
            await UpdatePageInformation();
            _isAuthenticated = await CurrentUserService.IsAuthenticated();
            StateHasChanged();
        }
    
        public async Task UpdatePageInformation()
        {
            _listSvarViewModel = await SvarService.GetListSvarViewModel(int.Parse(TraadId), false);
            _total = _listSvarViewModel.Svar.Count;
        }
    
        protected override void OnAfterRender(bool firstRender)
        {
            if (firstRender) return;
            _loaded = true;
            _svarCount = 0;
            StateHasChanged();
        }
    
        private async ValueTask<ItemsProviderResult<Svar>> LoadSvar(ItemsProviderRequest request)
        {
            var numSvar = Math.Min(request.Count, _total - request.StartIndex);
            var listOfSvar = _listSvarViewModel.Svar.GetRange(request.StartIndex, numSvar);
            return new ItemsProviderResult<Svar>(listOfSvar, _total);
        }
    }

enter image description here

As you can see it doesn't load any more items.

It fires the LoadSvar method on render, which renders 35 out of more than 400 items. But scrolling doesn't fire the method again to load more.

I'm pulling my hair out here. The out commented foreach loop, is not fast enough, so I would really like to get the Virtualize component working.

2 Answers

Change OnAfterRender() and OnParametersSetAsync() to not call StateHasChanged() every time, which is not a good idea. Also, remove SetParametersAsync():

bool _loaded = false;

protected override async Task OnParametersSetAsync()
{
    await UpdatePageInformation();
    _isAuthenticated = await CurrentUserService.IsAuthenticated();
}

protected override void OnAfterRender(bool firstRender)
{
    if (firstRender)
    {
        _loaded = true;
        StateHasChanged();
    }
}

I believe this is very likely a CSS issue (which is also why this is so hard to debug) and your screenshot shows some custom styling which would substantiate this.

The Virtualize component will do an initial call to the ItemsProvider delegate to get a first batch of items. The number of items requested here depends on the current height of the component, the height of each item (50 px by default) and the over scan count. It is also through this initial call that the component learns the number of total items.

And here the issue begins. Because the Virtualize component now knows the total number of items, it can also calculate its own total height (including items which haven't been rendered yet). If the height of the Virtualize component is such that it grows indefinitely to fit its contents (which I assert is the case here), then there will be no scrollbar and since you cannot scroll, no additional provider calls are ever triggered. You will then also see a large space below the first 35 items which should match a height of (400 - 35) * 50px.

You can easily verify if this is the case by adding something like style="height:300px" to the Virtualize component. I also encourage you to use the browser's DOM explorer to verify the dimensions of the involved elements and to check which element specifically (if any) is showing a scrollbar when perhaps it should not.

Edit: Looking at your code again, I believe the col-xl-6 is most likely the culprit here. Assuming you use Bootstrap, col's are based on flexbox and flex items will typically grow indefinitely to match their contents.

Related