From 84819ee1c94a23361b2011485052c41c7f05f810 Mon Sep 17 00:00:00 2001 From: Vrajkumar Shah Date: Mon, 1 Jun 2026 18:46:11 +0530 Subject: [PATCH] perf: optimize Firestore reaction fetching using batched aggregation --- src/pages/Home.tsx | 45 ++++++++++++++++++++++++++++++++----------- src/pages/Stories.tsx | 43 +++++++++++++++++++++++++++++++++++------ 2 files changed, 71 insertions(+), 17 deletions(-) diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 8d4a746..d22b340 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -192,6 +192,7 @@ export default function Home() { const querySnapshot = await getDocs(q); const storiesData: Story[] = []; + const storyIds: string[] = []; for (const doc of querySnapshot.docs) { const storyData = { @@ -199,20 +200,42 @@ export default function Home() { ...doc.data(), reactionsCount: 0 } as Story; + storiesData.push(storyData); + storyIds.push(doc.id); + } - const reactionsRef = collection(db, 'reactions'); - const reactionsQuery = query( - reactionsRef, - where('story_id', '==', doc.id) - ); + // Collect reactions counts in-memory if there are stories + const reactionsCountMap: Record = {}; + if (storyIds.length > 0) { + const chunks: string[][] = []; + for (let i = 0; i < storyIds.length; i += 30) { + chunks.push(storyIds.slice(i, i + 30)); + } - const reactionsSnapshot = await getDocs(reactionsQuery); - const storyReactions = reactionsSnapshot.docs.filter( - reactionDoc => reactionDoc.data().story_id === doc.id - ); + for (const chunk of chunks) { + try { + const reactionsRef = collection(db, 'reactions'); + const reactionsQuery = query( + reactionsRef, + where('story_id', 'in', chunk) + ); + const reactionsSnapshot = await getDocs(reactionsQuery); + reactionsSnapshot.docs.forEach(reactionDoc => { + const data = reactionDoc.data(); + const storyId = data.story_id; + if (storyId) { + reactionsCountMap[storyId] = (reactionsCountMap[storyId] || 0) + 1; + } + }); + } catch (err) { + console.error('Error batch-fetching reactions chunk:', err); + } + } + } - storyData.reactionsCount = storyReactions.length; - storiesData.push(storyData); + // Merge counts back into stories + for (const story of storiesData) { + story.reactionsCount = reactionsCountMap[story.id] || 0; } storiesData.sort((a, b) => (b.reactionsCount ?? 0) - (a.reactionsCount ?? 0)); diff --git a/src/pages/Stories.tsx b/src/pages/Stories.tsx index ddfa737..168e8b4 100644 --- a/src/pages/Stories.tsx +++ b/src/pages/Stories.tsx @@ -148,16 +148,12 @@ export default function Stories() { const querySnapshot = await getDocs(q); const fetchedStories: Story[] = []; + const storyIds: string[] = []; // Process each story document for (const doc of querySnapshot.docs) { const storyData = doc.data(); - // Get reaction count - const reactionsRef = collection(db, 'reactions'); - const reactionsQuery = query(reactionsRef, where('story_id', '==', doc.id)); - const reactionsSnapshot = await getDocs(reactionsQuery); - fetchedStories.push({ id: doc.id, title: storyData.title || '', @@ -166,9 +162,44 @@ export default function Stories() { media_urls: storyData.media_urls || [], created_at: storyData.created_at, author_id: storyData.author_id || '', - reactionsCount: reactionsSnapshot.size, + reactionsCount: 0, risk_level: storyData.risk_level || 'LOW', }); + storyIds.push(doc.id); + } + + // Collect reactions counts in-memory if there are stories + const reactionsCountMap: Record = {}; + if (storyIds.length > 0) { + const chunks: string[][] = []; + for (let i = 0; i < storyIds.length; i += 30) { + chunks.push(storyIds.slice(i, i + 30)); + } + + for (const chunk of chunks) { + try { + const reactionsRef = collection(db, 'reactions'); + const reactionsQuery = query( + reactionsRef, + where('story_id', 'in', chunk) + ); + const reactionsSnapshot = await getDocs(reactionsQuery); + reactionsSnapshot.docs.forEach(reactionDoc => { + const data = reactionDoc.data(); + const storyId = data.story_id; + if (storyId) { + reactionsCountMap[storyId] = (reactionsCountMap[storyId] || 0) + 1; + } + }); + } catch (err) { + console.error('Error batch-fetching reactions chunk:', err); + } + } + } + + // Merge counts back into stories + for (const story of fetchedStories) { + story.reactionsCount = reactionsCountMap[story.id] || 0; } setStories(fetchedStories);