useInfiniteScroll
Triggers a load callback whenever the scroll position reaches a boundary of a scrollable element, enabling infinite scroll for lists and feeds. Supports all four scroll directions, pre-load distance, manual control, async callbacks, and a canLoadMore gate function to control when loading stops.
Scroll to the bottom of the list to load more items.
Call useInfiniteScroll with a ref to the scrollable element and an async onLoadMore callback. The callback fires automatically when the user scrolls to the bottom (default direction).
import { const useRef$: { <T = any>(initialValue: null): Ref$<T | null>; <T = any>(initialValue: T): Ref$<T>; <T = any>(): Ref$<T | null>;}
useRef$ } from "@usels/core";import { const useInfiniteScroll: (element: MaybeEventTarget<Element | Document | Window>, onLoadMore: (direction: UseInfiniteScrollDirection) => Awaitable<void>, options?: DeepMaybeObservable<UseInfiniteScrollOptions>) => UseInfiniteScrollReturn
useInfiniteScroll } from "@usels/web";
function function Component(): JSX.Element
Component() { const const el$: Ref$<HTMLDivElement | null>
el$ = useRef$<HTMLDivElement>(): Ref$<HTMLDivElement | null> (+2 overloads)
Core (framework-agnostic) version of useRef$.
Creates an observable element ref with opaque wrapping.
- non-null value →
Ref$<T>: current, get(), peek() return T
- null / no arg →
Ref$<T | null>: current, get(), peek() return T | null
Nullability is expressed via the type parameter, mirroring T | null at the call site.
useRef$<interface HTMLDivElement
HTMLDivElement>();
const { const isLoading$: ReadonlyObservable<boolean>
Currently loading
isLoading$ } = function useInfiniteScroll(element: MaybeEventTarget<Element | Document | Window>, onLoadMore: (direction: UseInfiniteScrollDirection) => Awaitable<void>, options?: DeepMaybeObservable<UseInfiniteScrollOptions>): UseInfiniteScrollReturn
Framework-agnostic infinite scroll core.
Triggers onLoadMore whenever the scroll position reaches the configured
boundary of a scrollable element. Composes createScroll and
createElementVisibility. Must be called inside a useScope factory.
useInfiniteScroll(const el$: Ref$<HTMLDivElement | null>
el$, async () => { const const newItems: any
newItems = await any
fetchNextPage(); any
items.any
push(...const newItems: any
newItems); });
return ( <JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div RefAttributes<HTMLDivElement>.ref?: Ref<HTMLDivElement> | undefined
Allows getting a ref to the component instance.
Once the component unmounts, React will set ref.current to null
(or call the ref with null if you passed a callback ref).
ref={const el$: Ref$<HTMLDivElement | null>
el$} HTMLAttributes<HTMLDivElement>.style?: CSSProperties | undefined
style={{ StandardLonghandProperties<string | number, string & {}>.height?: Property.Height<string | number> | undefined
This feature is well established and works across many devices and browser versions. It’s been available across browsers since July 2015.
Syntax: auto | <length-percentage [0,∞]> | min-content | max-content | fit-content | fit-content(<length-percentage [0,∞]>) | <calc-size()> | <anchor-size()>
Initial value: auto
| Chrome | Firefox | Safari | Edge | IE |
| :----: | :-----: | :----: | :----: | :---: |
| 1 | 1 | 1 | 12 | 4 |
height: 300, StandardLonghandProperties<string | number, string & {}>.overflowY?: Property.OverflowY | undefined
This feature is well established and works across many devices and browser versions. It’s been available across browsers since July 2015.
Syntax: visible | hidden | clip | scroll | auto
Initial value: visible
| Chrome | Firefox | Safari | Edge | IE |
| :----: | :-----: | :----: | :----: | :---: |
| 1 | 3.5 | 3 | 12 | 5 |
overflowY: "auto" }}> {/* list items */} {const isLoading$: ReadonlyObservable<boolean>
Currently loading
isLoading$.ImmutableObservableBase<boolean>.get(trackingType?: TrackingType | GetOptions): {}
get() && <JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div>Loading…</JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div>} </JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div> );}import { createRef$ } from "@usels/core";import { createInfiniteScroll } from "@usels/web";
function Component() { "use scope"; const el$ = createRef$<HTMLDivElement>();
const { isLoading$ } = createInfiniteScroll(el$, async () => { const newItems = await fetchNextPage(); items.push(...newItems); });
return ( <div ref={el$} style={{ height: 300, overflowY: "auto" }}> {/* list items */} {isLoading$.get() && <div>Loading…</div>} </div> );}Direction
Section titled “Direction”Use the direction option to trigger loading from any edge. Certain directions require specific CSS on the scrollable container so that new content prepends in the correct visual position.
| Direction | Required CSS |
|---|---|
bottom (default) | No special settings required |
top | display: flex; flex-direction: column-reverse; |
left | display: flex; flex-direction: row-reverse; |
right | display: flex; |
useInfiniteScroll( el$, async () => { /* load older messages */ }, { direction: "top" });With distance
Section titled “With distance”Set distance (in px) to start loading before the user actually reaches the boundary — useful for pre-fetching the next page early.
useInfiniteScroll(el$, onLoadMore, { distance: 200, // trigger 200px before the bottom edge});canLoadMore
Section titled “canLoadMore”Pass a canLoadMore function to control whether loading should trigger. When it returns false, the scroll listener is skipped entirely. Use this to stop loading once all pages have been fetched.
useInfiniteScroll( el$, async () => { const page = await fetchNextPage(); if (!page.hasMore) setHasMore(false); }, { canLoadMore: () => hasMore, });Minimum load interval
Section titled “Minimum load interval”Use the interval option to set a minimum number of milliseconds between consecutive load triggers.
useInfiniteScroll(el$, onLoadMore, { interval: 200, // minimum 200ms between consecutive loads (default: 100)});Manual load & reset
Section titled “Manual load & reset”load triggers a load imperatively; reset re-checks the current scroll position and triggers a load if the boundary condition is met.
const { load, reset, isLoading$ } = useInfiniteScroll(el$, async () => { await fetchNextPage();});Parameters
Section titled “Parameters”| Parameter | Type | Description |
|---|---|---|
element | MaybeEventTarget<Element | Document | Window> | - |
onLoadMore | (direction: UseInfiniteScrollDirection) => Awaitable<void> | - |
options | UseInfiniteScrollOptions (optional) | - |
UseInfiniteScrollOptions
Section titled “UseInfiniteScrollOptions”Returns
Section titled “Returns”UseInfiniteScrollReturn
| Name | Type | Description |
|---|---|---|
isLoading$ | ReadonlyObservable<boolean> | Currently loading |
load | () => Promise<void> | Manually trigger a load |
reset | () => void | Re-check scroll position and trigger load if needed |