Fix: Stabilize ModerationQueue data fetching

This commit is contained in:
gpt-engineer-app[bot]
2025-10-10 15:30:03 +00:00
parent a16154c3de
commit 01a5f38e4f

View File

@@ -105,6 +105,10 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
const itemsRef = useRef<ModerationItem[]>([]); const itemsRef = useRef<ModerationItem[]>([]);
const loadedIdsRef = useRef<Set<string>>(new Set()); const loadedIdsRef = useRef<Set<string>>(new Set());
const realtimeUpdateDebounceRef = useRef<Map<string, NodeJS.Timeout>>(new Map()); const realtimeUpdateDebounceRef = useRef<Map<string, NodeJS.Timeout>>(new Map());
const lastFetchTimeRef = useRef<number>(0);
const isMountingRef = useRef(true);
const initialFetchCompleteRef = useRef(false);
const FETCH_COOLDOWN_MS = 1000;
// Get admin settings for polling configuration // Get admin settings for polling configuration
const { const {
@@ -169,7 +173,16 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
return; 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; fetchInProgressRef.current = true;
lastFetchTimeRef.current = now;
console.log('🔍 fetchItems called:', { console.log('🔍 fetchItems called:', {
entityFilter, entityFilter,
@@ -610,9 +623,13 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
// Expose refresh method via ref // Expose refresh method via ref
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
refresh: () => { 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]); fetchItems(filtersRef.current.entityFilter, filtersRef.current.statusFilter, false);
}
}), []);
// Track if initial fetch has happened // Track if initial fetch has happened
const hasInitialFetchRef = useRef(false); const hasInitialFetchRef = useRef(false);
@@ -641,14 +658,27 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
useEffect(() => { useEffect(() => {
if (!user) return; if (!user) return;
// Phase 1: Initial fetch (run once)
if (!hasInitialFetchRef.current) { if (!hasInitialFetchRef.current) {
hasInitialFetchRef.current = true; hasInitialFetchRef.current = true;
fetchItems(debouncedEntityFilter, debouncedStatusFilter, false); isMountingRef.current = true;
} else {
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); debouncedFetchItems(debouncedEntityFilter, debouncedStatusFilter, true, activeTab);
} }
// eslint-disable-next-line react-hooks/exhaustive-deps }, [debouncedEntityFilter, debouncedStatusFilter, user, activeTab, fetchItems, debouncedFetchItems]);
}, [debouncedEntityFilter, debouncedStatusFilter, user, activeTab]);
// Polling for auto-refresh (only if realtime is disabled) // Polling for auto-refresh (only if realtime is disabled)
useEffect(() => { useEffect(() => {
@@ -1678,7 +1708,8 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
}, []); }, []);
const QueueContent = () => { const QueueContent = () => {
if (isInitialLoad && loading) { // Show skeleton during initial load OR during mounting phase
if ((isInitialLoad && loading) || (isMountingRef.current && !initialFetchCompleteRef.current)) {
return <QueueSkeleton count={5} />; return <QueueSkeleton count={5} />;
} }