Skip to content

Extras

InfiniteScrolling

Bit.BlazorUI.Extras

BitInfiniteScrolling is a container that enables scrolling through a list of items infinitely as long as there are items to fetch and render. An IntersectionObserver watches a sentinel element at the end of the list and asks the ItemsProvider for the next page as soon as it comes into view, so nothing is fetched before it is needed. It renders the loading, empty, end and error states out of the box, each of them replaceable by a template, and it can load on demand through a Load more button, run reversed to prepend older items while the scroll position stays where the user left it, or scroll sideways. A page in flight is cancelled when it is no longer needed, and the loaded items can be capped, keyed, driven from code and reset from a key.

Notes

To use this component, you need to install the Bit.BlazorUI.Extras(opens in a new tab) nuget package, as described in the Optional steps of the Getting started page.

Usage

Every example is live. Open its code to see exactly what produced the component running underneath.

Basic

The ItemsProvider is the only required parameter: it receives the number of the items already loaded as the Skip of its request and returns the next page. The component renders each returned item with its child content, appends it to the list, and then waits for the sentinel element at the end of the list to come into view again, so the next page is fetched only when the user actually gets there. While a page is in flight, the default Loading... text is rendered under the items.

Templates

The content of the component is replaceable: ItemTemplate (an alias of the child content) renders each item, and LoadingTemplate replaces the loading text while a page is being fetched. The plain-text variant of the latter is also configurable through the LoadingMessage parameter, which is enough when no markup is needed. Whatever they render, the state the list is in is also announced to screen readers by a live region the component keeps in the DOM.

Empty

The empty state is rendered only when the very first page comes back without a single item, and never alongside any item. Its text comes from EmptyMessage, and EmptyTemplate replaces it with markup of your own. Both lists below are fed by a provider that returns no item.


EmptyMessage:




EmptyTemplate:

PageSize

Setting PageSize sends the size of the page to the provider as the Count of its request, so the query knows how many items to take. It also tells the component where the data ends: a page that comes back with fewer items than requested is the last one, which saves the extra empty round trip that would otherwise be needed to discover the end. Once there is nothing left to fetch, the EndMessage is rendered after the last item and the sentinel stops being watched; EndTemplate replaces that message with markup of your own.


EndMessage:




EndTemplate:

Manual

The Manual parameter replaces the automatic loading with an explicit button: the first page is still fetched on its own, but every next one waits for a click. This is the accessible fallback of an infinite list - it keeps the page footer reachable and gives keyboard and screen reader users a real control to operate - and it is also the polite choice on metered connections. The label of the button comes from LoadMoreText, and LoadMoreTemplate replaces its content entirely. The button keeps its place while the page it asked for is loading, disabled rather than removed, so the keyboard focus that pressed it is not dropped back to the top of the page.


LoadMoreText:




LoadMoreTemplate:

Error handling

An items provider that throws no longer takes the whole list down with it: the exception is caught, the ErrorMessage is rendered with a retry button, and the sentinel stops being watched so a failing endpoint cannot turn the list into a request storm. Clicking retry (or calling LoadMoreAsync) requests the same page again and the list continues from where it stopped - the button stays where it is, disabled and still labelled RetryText, until that retry is over. Use ErrorTemplate, which receives the thrown exception, to render the failure your own way - it replaces the built-in retry button too, so it should offer a way back of its own. Both lists below fail once, on their second page.


ErrorMessage:




ErrorTemplate:

Reversed

The Reversed parameter turns the list into a chat or a log: the sentinel moves to the top, each page is prepended above the already rendered items, and the component starts out scrolled to the newest ones. Scrolling up loads the older items while the scroll position is corrected around every insertion, so the item the user is reading stays exactly where it is instead of being pushed down the viewport. The loading, end and Load more blocks follow the sentinel to the top, which is where they belong in this mode.

Message 31
Message 32
Message 33
Message 34
Message 35
Message 36
Message 37
Message 38
Message 39
Message 40

Scroller

By default the root element of the component is its own scroll container. ScrollerSelector points it at another one instead, and the window value (like document, body and html) means the page itself scrolls, which is the layout of a feed that has no box of its own. A component that hands its scrolling over this way stops clipping and scrolling its own content. RootMargin grows the area the sentinel is checked against, so 200px starts the next page 200 pixels before the end of the list becomes visible and the list stays ahead of the reader. LastElementHeight gives the sentinel a real height, which is the coarser way of triggering the load earlier, and Threshold then decides how much of that height has to be visible before the load starts. Preload fetches the first page during initialization so prerendered markup is not empty.

Item 0
Item 1
Item 2
Item 3
Item 4
Item 5
Item 6
Item 7
Item 8
Item 9
Item 10
Item 11
Item 12
Item 13
Item 14
Item 15
Item 16
Item 17
Item 18
Item 19
Item 20
Item 21
Item 22
Item 23
Item 24
Item 25
Item 26
Item 27
Item 28
Item 29
Item 30
Item 31
Item 32
Item 33
Item 34
Item 35
Item 36
Item 37
Item 38
Item 39
Item 40
Item 41
Item 42
Item 43
Item 44
Item 45
Item 46
Item 47
Item 48
Item 49

Events

OnItemsLoaded is invoked after each successful page with the items of that page only, which is where analytics, an updated counter, or a URL that reflects the loaded range belong. OnEnd fires once, when the provider reports that nothing is left, and OnError hands over the exception that a failed page threw.


Loaded pages: 0, loaded items: 0

Public members

A reference to the component exposes what it has loaded so far (Items, HasMore, IsLoading, TotalCount and the Error of the last failed page) and the methods to drive it: RefreshDataAsync throws the loaded items away and starts over from the first page, LoadMoreAsync fetches the next page the way reaching the end of the list does, AppendItemsAsync, PrependItemsAsync, SetItemsAsync and RemoveItemAsync change the loaded items without calling the provider at all, and ScrollToTopAsync, ScrollToBottomAsync, ScrollToOffsetAsync and GetScrollOffsetAsync read and move the scroll container - the last two are what restore the position of a list the user is coming back to. The MaxItems parameter used here also caps the list: the last page is narrowed down to what is still missing and nothing is requested afterwards, so the list never grows past the cap.



Items: 0   HasMore:   IsLoading:   Offset: 0

Provider result

A provider can return a BitInfiniteScrollingItemsProviderResult instead of a plain sequence. It is an IEnumerable of the same page, so nothing else changes, but it also carries HasMore and TotalCount. HasMore lets the source say for itself where the data ends, which is what a cursor-paged API knows and a short page cannot express; TotalCount is published on the TotalCount member of the component, which is how a list reports the progress it has made through a known total.


Loaded 0 of ? items.

Horizontal

Horizontal lays the items out in a row and moves the whole component onto the horizontal axis: the pages are fetched while scrolling sideways, the sentinel becomes a thin column at the end of the row - sized by LastElementWidth rather than by LastElementHeight - and ScrollToTopAsync, ScrollToBottomAsync and ScrollToOffsetAsync all work along that axis, right-to-left lists included. Give the items a flex: 0 0 auto so the row grows past the width of the container instead of squeezing them into it.

ItemKey

Without a key an item is matched by its position, so a page prepended above the rendered items rewrites the content of every node below it - which is both the slow way to insert a page and the reason the state a node holds on its own (the text in an input, a running animation, the caret) ends up next to the wrong item. ItemKey returns a stable key per item, and the items are matched by that key instead. Type a note next to a message below, then scroll up to load the older ones: the note stays with its own message.

Message 31
Message 32
Message 33
Message 34
Message 35
Message 36
Message 37
Message 38
Message 39
Message 40

ResetKey

A provider written as a lambda that captures a filter is the same method on every render, so a changed filter cannot be told apart from a re-render of the parent - and reloading on the delegate alone would throw the list away every time the parent renders. ResetKey is what settles it: hand the filter (or anything derived from it) to the component and every change of that value refreshes the list from its first page, exactly like calling RefreshDataAsync.


Style & Class

The Style and Class parameters style the root element, while Styles and Classes reach the inner parts of the component - the sentinel (LastElement), the Loading, Empty, End and Error containers, and the Button of the manual and retry states. The LastElementClass, LastElementStyle, LastElementHeight and LastElementWidth parameters remain the shorthand for the sentinel alone.

RTL

Set Dir to BitDir.Rtl to render the list right-to-left. The items, the status blocks and the button of the manual and retry states all flip along with it, and a horizontal list scrolls from the right.

API

Every parameter, public member, sub-class and enum this component exposes.

BitInfiniteScrolling parameters

Name Type Default value Description
ChildContent RenderFragment<TItem>? null The custom template to render each item.
Classes BitInfiniteScrollingClassStyles? null Custom CSS classes for different parts of the component.
EmptyMessage string There is no item The message to render when there is no item available and no EmptyTemplate is provided.
EmptyTemplate RenderFragment? null The custom template to render when there is no item available.
EndMessage string? null The message to render after the last page, when there is no more item to fetch. Nothing is rendered while both this parameter and the EndTemplate are empty.
EndTemplate RenderFragment? null The custom template to render after the last page, when there is no more item to fetch.
ErrorMessage string Failed to load the items. The message to render when the items provider throws and no ErrorTemplate is provided.
ErrorTemplate RenderFragment<Exception>? null The custom template to render when the items provider throws, receiving the thrown exception as its context. It replaces the default error message and its retry button.
Horizontal bool false Lays the list out along the horizontal axis, so the pages are fetched while scrolling sideways and every scroll operation of the component works on the horizontal axis of its scroll container. The root element becomes a flex row in this mode and the sentinel element is given a width instead of a height.
ItemKey Func<TItem, object>? null The function that returns a stable key for each item, which is rendered as the @key of that item. A keyed item is matched by its key instead of by its position, so a page prepended above the rendered items inserts new nodes rather than rewriting the content of every node below it.
ItemsProvider BitInfiniteScrollingItemsProvider<TItem>? null The item provider function that will be called when scrolling ends. It receives a BitInfiniteScrollingItemsProviderRequest and returns the items of that page, optionally as a BitInfiniteScrollingItemsProviderResult that also states where the data ends.
ItemTemplate RenderFragment<TItem>? null Alias for ChildContent.
LastElementClass string? null The CSS class of the last element that triggers the loading.
LastElementHeight string? null The height of the last element that triggers the loading.
LastElementStyle string? null The CSS style of the last element that triggers the loading.
LastElementWidth string? null The width of the last element that triggers the loading, which is the size along the scroll axis in the horizontal mode.
LoadingMessage string Loading... The message to render while loading the new items and no LoadingTemplate is provided.
LoadingTemplate RenderFragment? null The custom template to render while loading the new items.
LoadMoreTemplate RenderFragment? null The custom template of the button that loads the next page in the manual mode and retries a failed load.
LoadMoreText string Load more The text of the button that loads the next page in the manual mode. The button keeps its place, disabled, while the page it asked for is loading.
Manual bool false Replaces the automatic loading with an explicit button, so each page is fetched only when the user asks for it.
MaxItems int? null The maximum number of items to load. The component stops fetching new pages as soon as the number of the loaded items reaches this value, and the last page it requests is narrowed down to what is still missing, so the list never grows beyond the cap.
OnEnd EventCallback The callback that is invoked when the last page is loaded and there is no more item to fetch.
OnError EventCallback<Exception> The callback that is invoked when the items provider throws, receiving the thrown exception.
OnItemsLoaded EventCallback<IReadOnlyList<TItem>> The callback that is invoked after each successful load, receiving the newly loaded items of that page.
PageSize int 0 The number of the items to request in each page, which is sent to the items provider as the Count of its request. A provider returning fewer items than this value is considered the last page.
Preload bool false Pre-loads the data at the initialization of the component. Useful in prerendering mode.
ResetKey object? null An arbitrary value that resets the component whenever it changes: the loaded items are thrown away and the first page is fetched again from the items provider. It is what tells the component that a provider written as a lambda over a changed filter now answers differently.
Reversed bool false Prepends each loaded page before the already rendered items and moves the sentinel element to the top of the list, so scrolling up loads the older items of a chat or a log while the scroll position stays put. The list starts out scrolled to its newest items, and its root element becomes a flex column in this mode.
RootMargin string? null The rootMargin parameter of the IntersectionObserver, which grows (or shrinks) the area around the scroll viewport that the last element is checked against.
RetryText string Retry The text of the button that retries the failed load.
ScrollerSelector string? null The CSS selector of the scroll container, by default the root element of the component is selected for this purpose. The window, document, body and html values all select the viewport of the page itself.
Styles BitInfiniteScrollingClassStyles? null Custom CSS styles for different parts of the component.
Threshold decimal? null The threshold parameter for the IntersectionObserver that specifies a ratio of intersection area to total bounding box area of the last element. It defaults to 0, which fires as soon as a single pixel of the last element shows up.

BitInfiniteScrolling public members

Name Type Default value Description
Error Exception? null The exception of the last failed load, or null while the last load succeeded.
HasMore bool true Determines whether another page can still be fetched from the items provider.
IsLoading bool false Determines whether a page is currently being fetched from the items provider.
Items IReadOnlyList<TItem> The items loaded so far, in the order they are rendered.
TotalCount int? null The total number of the items of the data source, as it was last reported by a BitInfiniteScrollingItemsProviderResult returned from the items provider.
AppendItemsAsync Func<IEnumerable<TItem>, Task> Appends the provided items to the end of the already loaded items, without calling the items provider.
LoadMoreAsync Func<Task> Loads the next page of the items, the same way reaching the end of the list does. It is the way to load the next page in the manual mode, and to retry a failed load.
PrependItemsAsync Func<IEnumerable<TItem>, Task> Prepends the provided items to the beginning of the already loaded items, without calling the items provider. The scroll position is kept stable.
RemoveItemAsync Func<TItem, Task<bool>> Removes the first occurrence of the provided item from the loaded items, and reports whether it was found.
SetItemsAsync Func<IEnumerable<TItem>, Task> Replaces every loaded item with the provided ones, without calling the items provider. It is the way to filter, sort, deduplicate or patch the loaded items in place.
GetScrollOffsetAsync Func<Task<double>> Returns the current offset of the scroll container, in pixels, along the scroll axis of the component.
ScrollToOffsetAsync Func<double, bool, Task> Scrolls the scroll container to the provided offset, in pixels, along the scroll axis of the component.
RefreshDataAsync Func<Task> Refreshes the items and re-renders them from scratch.
ScrollToBottomAsync Func<bool, Task> Scrolls the scroll container to its bottom. Useful in the reversed (chat) mode.
ScrollToTopAsync Func<bool, Task> Scrolls the scroll container to its top.

BitComponentBase parameters

Name Type Default value Description
AriaLabel string? null Gets or sets the accessible label for the component, used by assistive technologies.
Class string? null Gets or sets the CSS class name(s) to apply to the rendered element.
Dir BitDir? null Gets or sets the text directionality for the component's content.
ForceAnimation bool false Gets or sets a value indicating whether the component's animations play at their full duration even when reduced motion is requested.
HtmlAttributes Dictionary<string, object> new Dictionary<string, object>() Captures additional HTML attributes to be applied to the rendered element, in addition to the component's parameters.
Id string? null Gets or sets the unique identifier for the component's root element.
IsEnabled bool true Gets or sets a value indicating whether the component is enabled and can respond to user interaction.
Style string? null Gets or sets the CSS style string to apply to the rendered element.
TabIndex string? null Gets or sets the tab order index for the component when navigating with the keyboard.
Visibility BitVisibility BitVisibility.Visible Gets or sets the visibility state (visible, hidden, or collapsed) of the component.

BitComponentBase public members

Name Type Default value Description
UniqueId Guid Guid.NewGuid() Gets the readonly unique identifier for the component's root element, assigned when the component instance is constructed.
RootElement ElementReference Gets the reference to the root HTML element associated with this component.

BitInfiniteScrollingClassStyles properties

Custom CSS classes/styles for different parts of the BitInfiniteScrolling.

Name Type Default value Description
Root string? null Custom CSS classes/styles for the root element of the BitInfiniteScrolling.
LastElement string? null Custom CSS classes/styles for the sentinel (last) element of the BitInfiniteScrolling that triggers the loading.
Loading string? null Custom CSS classes/styles for the loading container of the BitInfiniteScrolling.
Empty string? null Custom CSS classes/styles for the empty container of the BitInfiniteScrolling.
End string? null Custom CSS classes/styles for the end container of the BitInfiniteScrolling that renders after the last page.
Error string? null Custom CSS classes/styles for the error container of the BitInfiniteScrolling.
Button string? null Custom CSS classes/styles for the button of the BitInfiniteScrolling that loads the next page in manual mode and retries a failed load.

BitInfiniteScrollingItemsProviderResult<T> properties

The optional result of a BitInfiniteScrollingItemsProvider, which lets a provider state explicitly whether another page still exists and how many items the data source holds in total. It is itself an IEnumerable of the items of the page, so a provider can return one wherever a plain sequence is expected.

Name Type Default value Description
Items IEnumerable<T> The items of the requested page.
HasMore bool? null Whether another page can still be fetched, or null to let the component infer it from the size of the page: a page shorter than the requested count is the last one.
TotalCount int? null The total number of the items of the data source, when the provider knows it. The component exposes the last reported value through its TotalCount member.

BitInfiniteScrollingItemsProviderRequest properties

A request to a BitInfiniteScrollingItemsProvider for the next page of items.

Name Type Default value Description
Skip int The number of items already loaded, which is the index of the first item requested.
Count int The maximum number of items requested, which is the PageSize parameter of the component. It is 0 when no page size is configured.
CancellationToken CancellationToken A token that is cancelled when this request is no longer needed, for example when the data gets refreshed or the component gets disposed while the request is still in flight.

BitVisibility enum

Name Value Description
Visible 0 The content of the component is visible.
Hidden 1 The content of the component is hidden, but the space it takes on the page remains (visibility:hidden).
Collapsed 2 The component is hidden (display:none).

BitDir enum

Name Value Description
Ltr 0 Ltr (left to right) is to be used for languages that are written from the left to the right (like English).
Rtl 1 Rtl (right to left) is to be used for languages that are written from the right to the left (like Arabic).
Auto 2 Auto lets the user agent decide. It uses a basic algorithm as it parses the characters inside the element until it finds a character with a strong directionality, then applies that directionality to the whole element.

Feedback

Found a mistake, a gap, or something that could be clearer? Every page and every component is one click from its source.