diff --git a/src/hooks/useRealtimeModerationStats.ts b/src/hooks/useRealtimeModerationStats.ts index d62e6f2f..e20e1fb8 100644 --- a/src/hooks/useRealtimeModerationStats.ts +++ b/src/hooks/useRealtimeModerationStats.ts @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useState, useRef, useCallback } from 'react'; import { supabase } from '@/integrations/supabase/client'; import { RealtimeChannel } from '@supabase/supabase-js'; @@ -22,9 +22,15 @@ export const useRealtimeModerationStats = (options: UseRealtimeModerationStatsOp flaggedContent: 0, }); const [channel, setChannel] = useState(null); - const [updateTimer, setUpdateTimer] = useState(null); + const updateTimerRef = useRef(null); + const onStatsChangeRef = useRef(onStatsChange); + + // Update ref when callback changes + useEffect(() => { + onStatsChangeRef.current = onStatsChange; + }, [onStatsChange]); - const fetchStats = async () => { + const fetchStats = useCallback(async () => { try { const [submissionsResult, reportsResult, reviewsResult] = await Promise.all([ supabase @@ -48,19 +54,18 @@ export const useRealtimeModerationStats = (options: UseRealtimeModerationStatsOp }; setStats(newStats); - onStatsChange?.(newStats); + onStatsChangeRef.current?.(newStats); } catch (error) { console.error('Error fetching moderation stats:', error); } - }; + }, []); - const debouncedFetchStats = () => { - if (updateTimer) { - clearTimeout(updateTimer); + const debouncedFetchStats = useCallback(() => { + if (updateTimerRef.current) { + clearTimeout(updateTimerRef.current); } - const timer = setTimeout(fetchStats, debounceMs); - setUpdateTimer(timer); - }; + updateTimerRef.current = setTimeout(fetchStats, debounceMs); + }, [fetchStats, debounceMs]); useEffect(() => { if (!enabled) return; @@ -115,12 +120,12 @@ export const useRealtimeModerationStats = (options: UseRealtimeModerationStatsOp return () => { console.log('Cleaning up moderation stats realtime subscription'); - if (updateTimer) { - clearTimeout(updateTimer); + if (updateTimerRef.current) { + clearTimeout(updateTimerRef.current); } supabase.removeChannel(realtimeChannel); }; - }, [enabled]); + }, [enabled, fetchStats, debouncedFetchStats]); return { stats, refresh: fetchStats }; }; diff --git a/src/hooks/useRealtimeSubmissionItems.ts b/src/hooks/useRealtimeSubmissionItems.ts index 06c6af92..913a154e 100644 --- a/src/hooks/useRealtimeSubmissionItems.ts +++ b/src/hooks/useRealtimeSubmissionItems.ts @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useState, useRef } from 'react'; import { supabase } from '@/integrations/supabase/client'; import { RealtimeChannel } from '@supabase/supabase-js'; @@ -11,6 +11,14 @@ interface UseRealtimeSubmissionItemsOptions { export const useRealtimeSubmissionItems = (options: UseRealtimeSubmissionItemsOptions = {}) => { const { submissionId, onUpdate, enabled = true } = options; const [channel, setChannel] = useState(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(() => { if (!enabled || !submissionId) return; @@ -27,7 +35,7 @@ export const useRealtimeSubmissionItems = (options: UseRealtimeSubmissionItemsOp }, (payload) => { console.log('Submission item updated:', payload); - onUpdate?.(payload); + onUpdateRef.current?.(payload); } ) .subscribe((status) => { @@ -40,7 +48,7 @@ export const useRealtimeSubmissionItems = (options: UseRealtimeSubmissionItemsOp console.log('Cleaning up submission items realtime subscription'); supabase.removeChannel(realtimeChannel); }; - }, [submissionId, enabled, onUpdate]); + }, [submissionId, enabled]); return { channel }; }; diff --git a/src/hooks/useRealtimeSubmissions.ts b/src/hooks/useRealtimeSubmissions.ts index 0ec04b40..cdb16c24 100644 --- a/src/hooks/useRealtimeSubmissions.ts +++ b/src/hooks/useRealtimeSubmissions.ts @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useState, useRef } from 'react'; import { supabase } from '@/integrations/supabase/client'; import { RealtimeChannel } from '@supabase/supabase-js'; @@ -12,6 +12,18 @@ interface UseRealtimeSubmissionsOptions { export const useRealtimeSubmissions = (options: UseRealtimeSubmissionsOptions = {}) => { const { onInsert, onUpdate, onDelete, enabled = true } = options; const [channel, setChannel] = useState(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(() => { if (!enabled) return; @@ -27,7 +39,7 @@ export const useRealtimeSubmissions = (options: UseRealtimeSubmissionsOptions = }, (payload) => { console.log('Submission inserted:', payload); - onInsert?.(payload); + onInsertRef.current?.(payload); } ) .on( @@ -39,7 +51,7 @@ export const useRealtimeSubmissions = (options: UseRealtimeSubmissionsOptions = }, (payload) => { console.log('Submission updated:', payload); - onUpdate?.(payload); + onUpdateRef.current?.(payload); } ) .on( @@ -51,7 +63,7 @@ export const useRealtimeSubmissions = (options: UseRealtimeSubmissionsOptions = }, (payload) => { console.log('Submission deleted:', payload); - onDelete?.(payload); + onDeleteRef.current?.(payload); } ) .subscribe((status) => { @@ -64,7 +76,7 @@ export const useRealtimeSubmissions = (options: UseRealtimeSubmissionsOptions = console.log('Cleaning up submissions realtime subscription'); supabase.removeChannel(realtimeChannel); }; - }, [enabled, onInsert, onUpdate, onDelete]); + }, [enabled]); return { channel }; };