Skip to content

useTimeline

function useTimeline<TCustom>(uid, opts?): object;

useFeed('timeline', uid, opts) — the feed aggregating who this user follows.

Type Parameter Default type
TCustom Record<string, unknown>
Parameter Type
uid string
opts? UseFeedOptions<TCustom>
activities: Activity<TCustom>[];
addActivity: (a) => Promise<Activity<TCustom> | undefined>;
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 -

Promise<Activity<TCustom> | undefined>

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: () => Promise<void>;

Promise<void>

enabled: boolean;
error: Error | null;
hasNext: boolean;
isLoading: boolean;
isLoadingInitial: boolean;
isLoadingMore: boolean;
items: FeedItem<TCustom>[];

activities with promoted rows interleaved per the placement props.

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.

Promise<void>

newCount: number = pending.length;
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).

promoted: PromotedActivity<TCustom>[];

The eligible promoted set for this reader — NOT placed. Empty when none.

refresh: () => Promise<void>;

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.

Promise<void>

revalidateObjects: () => Promise<void>;

Re-read the shown activities’ objects on demand, without touching pagination — see the JSDoc above the callback.

Promise<void>

showNew: () => void;

void

trackPromotedClick: (p) => void;

Call from your row’s click handler; forwards to onPromotedClick.

Parameter Type
p PromotedActivity<TCustom>

void

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.

Parameter Type
activityId string
body PatchBody
callOpts? { onError?: OptimisticOnError; }
callOpts.onError? OptimisticOnError

Promise<Activity<TCustom> | undefined>