Fix realtime subscription loop

This commit is contained in:
gpt-engineer-app[bot]
2025-10-01 20:34:56 +00:00
parent 65c3e92c70
commit ef4663b09f
3 changed files with 47 additions and 22 deletions

View File

@@ -1,4 +1,4 @@
import { useEffect, useState } from 'react'; import { useEffect, useState, useRef, useCallback } from 'react';
import { supabase } from '@/integrations/supabase/client'; import { supabase } from '@/integrations/supabase/client';
import { RealtimeChannel } from '@supabase/supabase-js'; import { RealtimeChannel } from '@supabase/supabase-js';
@@ -22,9 +22,15 @@ export const useRealtimeModerationStats = (options: UseRealtimeModerationStatsOp
flaggedContent: 0, flaggedContent: 0,
}); });
const [channel, setChannel] = useState<RealtimeChannel | null>(null); const [channel, setChannel] = useState<RealtimeChannel | null>(null);
const [updateTimer, setUpdateTimer] = useState<NodeJS.Timeout | null>(null); const updateTimerRef = useRef<NodeJS.Timeout | null>(null);
const onStatsChangeRef = useRef(onStatsChange);
// Update ref when callback changes
useEffect(() => {
onStatsChangeRef.current = onStatsChange;
}, [onStatsChange]);
const fetchStats = async () => { const fetchStats = useCallback(async () => {
try { try {
const [submissionsResult, reportsResult, reviewsResult] = await Promise.all([ const [submissionsResult, reportsResult, reviewsResult] = await Promise.all([
supabase supabase
@@ -48,19 +54,18 @@ export const useRealtimeModerationStats = (options: UseRealtimeModerationStatsOp
}; };
setStats(newStats); setStats(newStats);
onStatsChange?.(newStats); onStatsChangeRef.current?.(newStats);
} catch (error) { } catch (error) {
console.error('Error fetching moderation stats:', error); console.error('Error fetching moderation stats:', error);
} }
}; }, []);
const debouncedFetchStats = () => { const debouncedFetchStats = useCallback(() => {
if (updateTimer) { if (updateTimerRef.current) {
clearTimeout(updateTimer); clearTimeout(updateTimerRef.current);
} }
const timer = setTimeout(fetchStats, debounceMs); updateTimerRef.current = setTimeout(fetchStats, debounceMs);
setUpdateTimer(timer); }, [fetchStats, debounceMs]);
};
useEffect(() => { useEffect(() => {
if (!enabled) return; if (!enabled) return;
@@ -115,12 +120,12 @@ export const useRealtimeModerationStats = (options: UseRealtimeModerationStatsOp
return () => { return () => {
console.log('Cleaning up moderation stats realtime subscription'); console.log('Cleaning up moderation stats realtime subscription');
if (updateTimer) { if (updateTimerRef.current) {
clearTimeout(updateTimer); clearTimeout(updateTimerRef.current);
} }
supabase.removeChannel(realtimeChannel); supabase.removeChannel(realtimeChannel);
}; };
}, [enabled]); }, [enabled, fetchStats, debouncedFetchStats]);
return { stats, refresh: fetchStats }; return { stats, refresh: fetchStats };
}; };

View File

@@ -1,4 +1,4 @@
import { useEffect, useState } from 'react'; import { useEffect, useState, useRef } from 'react';
import { supabase } from '@/integrations/supabase/client'; import { supabase } from '@/integrations/supabase/client';
import { RealtimeChannel } from '@supabase/supabase-js'; import { RealtimeChannel } from '@supabase/supabase-js';
@@ -11,6 +11,14 @@ interface UseRealtimeSubmissionItemsOptions {
export const useRealtimeSubmissionItems = (options: UseRealtimeSubmissionItemsOptions = {}) => { export const useRealtimeSubmissionItems = (options: UseRealtimeSubmissionItemsOptions = {}) => {
const { submissionId, onUpdate, enabled = true } = options; const { submissionId, onUpdate, enabled = true } = options;
const [channel, setChannel] = useState<RealtimeChannel | null>(null); const [channel, setChannel] = useState<RealtimeChannel | null>(null);
// Use ref to store latest callback without triggering re-subscriptions
const onUpdateRef = useRef(onUpdate);
// Update ref when callback changes
useEffect(() => {
onUpdateRef.current = onUpdate;
}, [onUpdate]);
useEffect(() => { useEffect(() => {
if (!enabled || !submissionId) return; if (!enabled || !submissionId) return;
@@ -27,7 +35,7 @@ export const useRealtimeSubmissionItems = (options: UseRealtimeSubmissionItemsOp
}, },
(payload) => { (payload) => {
console.log('Submission item updated:', payload); console.log('Submission item updated:', payload);
onUpdate?.(payload); onUpdateRef.current?.(payload);
} }
) )
.subscribe((status) => { .subscribe((status) => {
@@ -40,7 +48,7 @@ export const useRealtimeSubmissionItems = (options: UseRealtimeSubmissionItemsOp
console.log('Cleaning up submission items realtime subscription'); console.log('Cleaning up submission items realtime subscription');
supabase.removeChannel(realtimeChannel); supabase.removeChannel(realtimeChannel);
}; };
}, [submissionId, enabled, onUpdate]); }, [submissionId, enabled]);
return { channel }; return { channel };
}; };

View File

@@ -1,4 +1,4 @@
import { useEffect, useState } from 'react'; import { useEffect, useState, useRef } from 'react';
import { supabase } from '@/integrations/supabase/client'; import { supabase } from '@/integrations/supabase/client';
import { RealtimeChannel } from '@supabase/supabase-js'; import { RealtimeChannel } from '@supabase/supabase-js';
@@ -12,6 +12,18 @@ interface UseRealtimeSubmissionsOptions {
export const useRealtimeSubmissions = (options: UseRealtimeSubmissionsOptions = {}) => { export const useRealtimeSubmissions = (options: UseRealtimeSubmissionsOptions = {}) => {
const { onInsert, onUpdate, onDelete, enabled = true } = options; const { onInsert, onUpdate, onDelete, enabled = true } = options;
const [channel, setChannel] = useState<RealtimeChannel | null>(null); const [channel, setChannel] = useState<RealtimeChannel | null>(null);
// Use refs to store latest callbacks without triggering re-subscriptions
const onInsertRef = useRef(onInsert);
const onUpdateRef = useRef(onUpdate);
const onDeleteRef = useRef(onDelete);
// Update refs when callbacks change
useEffect(() => {
onInsertRef.current = onInsert;
onUpdateRef.current = onUpdate;
onDeleteRef.current = onDelete;
}, [onInsert, onUpdate, onDelete]);
useEffect(() => { useEffect(() => {
if (!enabled) return; if (!enabled) return;
@@ -27,7 +39,7 @@ export const useRealtimeSubmissions = (options: UseRealtimeSubmissionsOptions =
}, },
(payload) => { (payload) => {
console.log('Submission inserted:', payload); console.log('Submission inserted:', payload);
onInsert?.(payload); onInsertRef.current?.(payload);
} }
) )
.on( .on(
@@ -39,7 +51,7 @@ export const useRealtimeSubmissions = (options: UseRealtimeSubmissionsOptions =
}, },
(payload) => { (payload) => {
console.log('Submission updated:', payload); console.log('Submission updated:', payload);
onUpdate?.(payload); onUpdateRef.current?.(payload);
} }
) )
.on( .on(
@@ -51,7 +63,7 @@ export const useRealtimeSubmissions = (options: UseRealtimeSubmissionsOptions =
}, },
(payload) => { (payload) => {
console.log('Submission deleted:', payload); console.log('Submission deleted:', payload);
onDelete?.(payload); onDeleteRef.current?.(payload);
} }
) )
.subscribe((status) => { .subscribe((status) => {
@@ -64,7 +76,7 @@ export const useRealtimeSubmissions = (options: UseRealtimeSubmissionsOptions =
console.log('Cleaning up submissions realtime subscription'); console.log('Cleaning up submissions realtime subscription');
supabase.removeChannel(realtimeChannel); supabase.removeChannel(realtimeChannel);
}; };
}, [enabled, onInsert, onUpdate, onDelete]); }, [enabled]);
return { channel }; return { channel };
}; };