From 01a5f38e4f2810c2a95b52e8334ceaf6358e62c0 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Fri, 10 Oct 2025 15:30:03 +0000 Subject: [PATCH] Fix: Stabilize ModerationQueue data fetching --- src/components/moderation/ModerationQueue.tsx | 45 ++++++++++++++++--- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/src/components/moderation/ModerationQueue.tsx b/src/components/moderation/ModerationQueue.tsx index 9e7b8363..82db91aa 100644 --- a/src/components/moderation/ModerationQueue.tsx +++ b/src/components/moderation/ModerationQueue.tsx @@ -105,6 +105,10 @@ export const ModerationQueue = forwardRef((props, ref) => { const itemsRef = useRef([]); const loadedIdsRef = useRef>(new Set()); const realtimeUpdateDebounceRef = useRef>(new Map()); + const lastFetchTimeRef = useRef(0); + const isMountingRef = useRef(true); + const initialFetchCompleteRef = useRef(false); + const FETCH_COOLDOWN_MS = 1000; // Get admin settings for polling configuration const { @@ -169,7 +173,16 @@ export const ModerationQueue = forwardRef((props, ref) => { return; } + // Cooldown check - prevent rapid-fire calls + const now = Date.now(); + const timeSinceLastFetch = now - lastFetchTimeRef.current; + if (timeSinceLastFetch < FETCH_COOLDOWN_MS && lastFetchTimeRef.current > 0) { + console.log(`⏸️ Fetch cooldown active (${timeSinceLastFetch}ms since last fetch), skipping`); + return; + } + fetchInProgressRef.current = true; + lastFetchTimeRef.current = now; console.log('🔍 fetchItems called:', { entityFilter, @@ -610,9 +623,13 @@ export const ModerationQueue = forwardRef((props, ref) => { // Expose refresh method via ref useImperativeHandle(ref, () => ({ refresh: () => { - fetchItems(filtersRef.current.entityFilter, filtersRef.current.statusFilter, false); // Manual refresh shows loading + if (isMountingRef.current) { + console.log('⏭️ Ignoring refresh during mount phase'); + return; + } + fetchItems(filtersRef.current.entityFilter, filtersRef.current.statusFilter, false); } - }), [fetchItems]); + }), []); // Track if initial fetch has happened const hasInitialFetchRef = useRef(false); @@ -641,14 +658,27 @@ export const ModerationQueue = forwardRef((props, ref) => { useEffect(() => { if (!user) return; + // Phase 1: Initial fetch (run once) if (!hasInitialFetchRef.current) { hasInitialFetchRef.current = true; - fetchItems(debouncedEntityFilter, debouncedStatusFilter, false); - } else { + isMountingRef.current = true; + + fetchItems(debouncedEntityFilter, debouncedStatusFilter, false) + .then(() => { + initialFetchCompleteRef.current = true; + // Wait for DOM to paint before allowing subsequent fetches + requestAnimationFrame(() => { + isMountingRef.current = false; + }); + }); + return; // Exit early, don't respond to filter changes yet + } + + // Phase 2: Filter changes (only after initial fetch completes) + if (!isMountingRef.current && initialFetchCompleteRef.current) { debouncedFetchItems(debouncedEntityFilter, debouncedStatusFilter, true, activeTab); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [debouncedEntityFilter, debouncedStatusFilter, user, activeTab]); + }, [debouncedEntityFilter, debouncedStatusFilter, user, activeTab, fetchItems, debouncedFetchItems]); // Polling for auto-refresh (only if realtime is disabled) useEffect(() => { @@ -1678,7 +1708,8 @@ export const ModerationQueue = forwardRef((props, ref) => { }, []); const QueueContent = () => { - if (isInitialLoad && loading) { + // Show skeleton during initial load OR during mounting phase + if ((isInitialLoad && loading) || (isMountingRef.current && !initialFetchCompleteRef.current)) { return ; }