useInfiniteFeed
function useInfiniteFeed<TCustom>( group, id, opts?): object;useFeed with the scroll wiring attached: everything that hook returns, plus a
sentinelRef to put on a trailing element and an onEndReached for React Native.
const { activities, sentinelRef, isLoadingInitial, error, retry } = useInfiniteFeed('timeline', uid)if (isLoadingInitial && activities.length === 0) return <FullPageLoading />return <> {activities.map((a) => <Row key={a.id} activity={a} />)} {error && <button onClick={() => void retry()}>Try again</button>} <div ref={sentinelRef} /></>The paging rules all live in useFeed — in-flight guard, id dedupe, the error guard
that retry() clears. This adds only the two things a sentinel needs on top:
- It re-checks after every page. The sentinel does not move when a page lands, so no new intersection event fires and one-page-per-gesture would be the ceiling.
- It reads
canLoadMore/loadNextthrough refs. The observer outlives the render that created it, so a captured closure would page against a stale cursor.
Where there is no IntersectionObserver (React Native, SSR), sentinelRef is an inert
no-op and onEndReached — wired to FlatList’s prop of the same name — is the path.
Type Parameters
Section titled “Type Parameters”| Type Parameter | Default type |
|---|---|
TCustom |
Record<string, unknown> |
Parameters
Section titled “Parameters”| Parameter | Type |
|---|---|
group |
string |
id |
string |
opts? |
UseInfiniteFeedOptions<TCustom> |
Returns
Section titled “Returns”activities
Section titled “activities”activities: Activity<TCustom>[];addActivity
Section titled “addActivity”addActivity: (a) => Promise<Activity<TCustom> | undefined>;Parameters
Section titled “Parameters”| Parameter | Type | Description |
|---|---|---|
a |
{ custom?: TCustom; foreign_id?: string | null; object: string; refs?: string[]; target?: string | null; time?: string; verb: string; } |
- |
a.custom? |
TCustom |
- |
a.foreign_id? |
string | null |
- |
a.object |
string |
- |
a.refs? |
string[] |
Objects this activity points at, as type:id. Max 4. Resolved into objects above. |
a.target? |
string | null |
- |
a.time? |
string |
- |
a.verb |
string |
- |
Returns
Section titled “Returns”Promise<Activity<TCustom> | undefined>
canLoadMore
Section titled “canLoadMore”canLoadMore: boolean;Bind an infinite-scroll sentinel to THIS, not to hasNext: it folds in the two
states where another fetch would be wrong — one already in flight, and an
unacknowledged error on the same cursor.
checkNew
Section titled “checkNew”checkNew: () => Promise<void>;Returns
Section titled “Returns”Promise<void>
enabled
Section titled “enabled”enabled: boolean;error: Error | null;hasNext
Section titled “hasNext”hasNext: boolean;isLoading
Section titled “isLoading”isLoading: boolean;isLoadingInitial
Section titled “isLoadingInitial”isLoadingInitial: boolean;isLoadingMore
Section titled “isLoadingMore”isLoadingMore: boolean;items: FeedItem<TCustom>[];activities with promoted rows interleaved per the placement props.
loadNext
Section titled “loadNext”loadNext: () => Promise<void>;Append the next page. Safe to call from an IntersectionObserver: it is a no-op at
end-of-feed, while a page is already in flight, and while error is set.
The error guard is what stops a sentinel from hammering: a failed page leaves next
unchanged, so an unguarded retry re-issues the identical request for as long as the
sentinel stays intersecting — which, with the list short one page, is forever. Call
retry() to clear the error and try that same cursor again.
Returns
Section titled “Returns”Promise<void>
newCount
Section titled “newCount”newCount: number = pending.length;objects
Section titled “objects”objects: Record<string, DropInObject<TCustom>>;Refs sidecar for the currently-shown page(s), keyed type:id. {} when the
server sent none — never undefined. Resolve an activity’s refs against it with
resolveRefs(activity, objects).
onEndReached
Section titled “onEndReached”onEndReached: () => void;<FlatList onEndReached={onEndReached} onEndReachedThreshold={0.5} />. Guarded the
same way as the sentinel, because FlatList fires it repeatedly near the end.
Returns
Section titled “Returns”void
promoted
Section titled “promoted”promoted: PromotedActivity<TCustom>[];The eligible promoted set for this reader — NOT placed. Empty when none.
refresh
Section titled “refresh”refresh: () => Promise<void>;Returns
Section titled “Returns”Promise<void>
retry: () => Promise<void>;Clear the error and re-issue whatever failed. The explicit escape from loadNext’s
error guard — wire it to a “Try again” button, never to the sentinel.
Which read it re-issues depends on what broke. A failed page 2+ has a cursor to ask
for again, so it re-fetches that. A failed FIRST page never set one — next is still
null — so a cursored refetch has nothing to send and would silently no-op, leaving
the error terminal and a remount the only way out. That case delegates to refresh(),
which re-reads page 1 uncursored. Defined after refresh for that reason.
next === null also means end-of-feed, where retry is meaningless: the hadError
check keeps that a no-op rather than turning an exhausted feed into a silent refetch.
Returns
Section titled “Returns”Promise<void>
revalidateObjects
Section titled “revalidateObjects”revalidateObjects: () => Promise<void>;Re-read the shown activities’ objects on demand, without touching pagination — see the JSDoc above the callback.
Returns
Section titled “Returns”Promise<void>
sentinelRef
Section titled “sentinelRef”sentinelRef: (node) => void;Attach to a trailing element. A callback ref, not an object ref, so it also fires when the sentinel is conditionally unmounted and remounted.
Parameters
Section titled “Parameters”| Parameter | Type |
|---|---|
node |
Element | null |
Returns
Section titled “Returns”void
showNew
Section titled “showNew”showNew: () => void;Returns
Section titled “Returns”void
trackPromotedClick
Section titled “trackPromotedClick”trackPromotedClick: (p) => void;Call from your row’s click handler; forwards to onPromotedClick.
Parameters
Section titled “Parameters”| Parameter | Type |
|---|---|
p |
PromotedActivity<TCustom> |
Returns
Section titled “Returns”void
updateActivity
Section titled “updateActivity”updateActivity: (activityId, body, callOpts?) => Promise<Activity<TCustom> | undefined>;Optimistic custom patch — see the JSDoc above the callback for the full
reject-after-rollback / onError contract.
Parameters
Section titled “Parameters”| Parameter | Type |
|---|---|
activityId |
string |
body |
PatchBody |
callOpts? |
{ onError?: OptimisticOnError; } |
callOpts.onError? |
OptimisticOnError |
Returns
Section titled “Returns”Promise<Activity<TCustom> | undefined>