useCircleFeedState
Location: src/hooks/useCircleFeedState.js
Overview
Provides hooks for circle-specific feed pacing. Each circle maintains its own "caught up" state so users can see which circles have new content. Includes useCircleFeedState for a single circle's state, useCircleNewPostCount for new post counts, useMarkCircleChecked for marking a circle as read, and useAllCirclesNewPostCounts for aggregated counts across all circles.
Refreshes use visibilitychange events (when the user returns to the tab) instead of polling, in line with Invariant #12 (Slowness Is Baked In).
Exported Hooks
useCircleFeedState
Gets the feed state for a specific circle.
function useCircleFeedState(circleId: string): {
loading: boolean,
error: string | null,
feedState: object | null,
lastCheckedAt: Date,
refetch: () => Promise<void>
}
| Parameter | Type | Required | Description |
|---|---|---|---|
circleId | string | Yes | The circle ID |
| Property | Type | Description |
|---|---|---|
feedState | object | null | Raw feed state row from circle_feed_state table |
lastCheckedAt | Date | When the user last viewed this circle's feed. Defaults to epoch if never checked. |
useCircleNewPostCount
Gets the number of new posts in a specific circle since last checked.
function useCircleNewPostCount(circleId: string): {
loading: boolean,
error: string | null,
newCount: number,
refetch: () => Promise<void>
}
| Property | Type | Description |
|---|---|---|
newCount | number | Number of new posts since last checked |
useMarkCircleChecked
Marks a circle as checked (caught up).
function useMarkCircleChecked(): {
markChecked: (circleId: string) => Promise<{ success?: boolean, error?: string }>,
loading: boolean,
error: string | null
}
| Property | Type | Description |
|---|---|---|
markChecked | (circleId) => Promise<Result> | Mark a circle as checked via mark_circle_checked RPC |
useAllCirclesNewPostCounts
Gets new post counts for all of the user's circles at once.
function useAllCirclesNewPostCounts(): {
loading: boolean,
error: string | null,
counts: Array<{ circle_id: string, new_count: number }>,
getNewCount: (circleId: string) => number,
getTotalNewCount: () => number,
refetch: () => Promise<void>
}
| Property | Type | Description |
|---|---|---|
counts | Array | Array of { circle_id, new_count } objects |
getNewCount | (circleId) => number | Get the new post count for a specific circle |
getTotalNewCount | () => number | Get total new posts across all circles |
Usage
import {
useCircleNewPostCount,
useMarkCircleChecked,
useAllCirclesNewPostCounts
} from '../hooks/useCircleFeedState';
function CircleNav({ circles }) {
const { getNewCount, getTotalNewCount } = useAllCirclesNewPostCounts();
return (
<nav>
<span>Total new: {getTotalNewCount()}</span>
{circles.map(c => (
<a key={c.id} href={`/circles/${c.id}`}>
{c.name} {getNewCount(c.id) > 0 && `(${getNewCount(c.id)} new)`}
</a>
))}
</nav>
);
}
function CircleFeed({ circleId }) {
const { newCount } = useCircleNewPostCount(circleId);
const { markChecked } = useMarkCircleChecked();
useEffect(() => {
// Mark as checked when viewing the feed
markChecked(circleId);
}, [circleId]);
return <Feed circleId={circleId} />;
}
Notes
- No polling is used.
useCircleNewPostCountanduseAllCirclesNewPostCountsrefresh when the browser tab becomes visible viadocument.visibilitychange. lastCheckedAtdefaults to epoch (new Date(0)) if the user has never viewed a circle, so all posts will count as new.- New post counts are fetched via the
get_circle_new_post_countandget_all_circles_new_post_countsRPC functions. - The current user is fetched internally by each hook via
supabase.auth.getUser().
Last updated: 2026-02-07