64 lines
2.3 KiB
TypeScript
64 lines
2.3 KiB
TypeScript
import { useMemo, useSyncExternalStore } from 'react';
|
|
import { useRedditStore } from '../state';
|
|
import type { RedditPost } from '../types';
|
|
import { getPostsSync, subscribePosts } from '../data/loader';
|
|
|
|
function isRedditPost(value: RedditPost | null | undefined): value is RedditPost {
|
|
return Boolean(value);
|
|
}
|
|
|
|
// Stable empty fallback so useSyncExternalStore's snapshot identity stays
|
|
// fixed before loadPosts() resolves — otherwise React would re-render on
|
|
// every commit (different `[]` reference each call).
|
|
const EMPTY_POSTS: RedditPost[] = Object.freeze([]) as readonly RedditPost[] as RedditPost[];
|
|
|
|
const getFixtureSnapshot = (): RedditPost[] => getPostsSync() ?? EMPTY_POSTS;
|
|
|
|
export function useFixturePosts(): RedditPost[] {
|
|
return useSyncExternalStore(subscribePosts, getFixtureSnapshot, getFixtureSnapshot);
|
|
}
|
|
|
|
export function useRedditPosts(): RedditPost[] {
|
|
const fixture = useFixturePosts();
|
|
const postsOverlay = useRedditStore((state) => state.posts);
|
|
const postIds = useRedditStore((state) => state.user.postIds);
|
|
|
|
return useMemo(() => {
|
|
const fixtureById = new Map(fixture.map((post) => [String(post.id), post]));
|
|
const resolvePost = (id: string): RedditPost | null => {
|
|
if (Object.prototype.hasOwnProperty.call(postsOverlay, id)) {
|
|
return postsOverlay[id];
|
|
}
|
|
return fixtureById.get(id) ?? null;
|
|
};
|
|
const indexedPosts = postIds.map((id) => resolvePost(id)).filter(isRedditPost);
|
|
const seen = new Set(indexedPosts.map((post) => post.id));
|
|
const fixturePosts = fixture
|
|
.map((post) => {
|
|
const id = String(post.id);
|
|
if (Object.prototype.hasOwnProperty.call(postsOverlay, id)) {
|
|
const overlay = postsOverlay[id];
|
|
if (overlay === null) return null;
|
|
return overlay;
|
|
}
|
|
return post;
|
|
})
|
|
.filter(isRedditPost)
|
|
.filter((post) => {
|
|
if (seen.has(post.id)) return false;
|
|
seen.add(post.id);
|
|
return true;
|
|
});
|
|
return [...indexedPosts, ...fixturePosts];
|
|
}, [postIds, postsOverlay, fixture]);
|
|
}
|
|
|
|
export function useRedditPostById(id: string | null | undefined): RedditPost | null {
|
|
const posts = useRedditPosts();
|
|
|
|
return useMemo(() => {
|
|
if (!id) return null;
|
|
return posts.find((post) => post.id === id) ?? null;
|
|
}, [id, posts]);
|
|
}
|