mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-21 09:31:13 -05:00
Fix: Stabilize ModerationQueue data fetching
This commit is contained in:
@@ -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(filtersRef.current.entityFilter, filtersRef.current.statusFilter, false);
|
||||||
}
|
}
|
||||||
}), [fetchItems]);
|
}), []);
|
||||||
|
|
||||||
// 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} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user