6.7 KiB
Critical Fixes - Quick Reference Guide
🚨 Top 5 Critical Fixes (Do These First)
1. Fix useNotifications Memory Leak ⚠️ CRITICAL
File: hooks/use-notifications.ts
Line: 239-255
Problem: Cleanup function not properly placed, causing memory leaks
Quick Fix:
useEffect(() => {
if (status !== 'authenticated' || !session?.user) return;
isMountedRef.current = true;
// Initial fetch
fetchNotificationCount(true);
fetchNotifications();
// Start polling with proper cleanup
const intervalId = setInterval(() => {
if (isMountedRef.current) {
debouncedFetchCount();
}
}, POLLING_INTERVAL);
// ✅ Proper cleanup
return () => {
isMountedRef.current = false;
clearInterval(intervalId);
};
}, [status, session?.user?.id]); // ✅ Only primitive dependencies
2. Fix Notification Badge Double Fetching ⚠️ CRITICAL
File: components/notification-badge.tsx
Lines: 65-70, 82-87, 92-99
Problem: Three different places trigger the same fetch simultaneously
Quick Fix:
// Add at top of component
const fetchInProgressRef = useRef(false);
const lastFetchRef = useRef<number>(0);
const FETCH_COOLDOWN = 1000; // 1 second cooldown
const manualFetch = async () => {
const now = Date.now();
// Prevent duplicate fetches
if (fetchInProgressRef.current) {
console.log('[NOTIFICATION_BADGE] Fetch already in progress');
return;
}
// Cooldown check
if (now - lastFetchRef.current < FETCH_COOLDOWN) {
console.log('[NOTIFICATION_BADGE] Too soon since last fetch');
return;
}
fetchInProgressRef.current = true;
lastFetchRef.current = now;
try {
await fetchNotifications(1, 10);
} finally {
fetchInProgressRef.current = false;
}
};
// Remove duplicate useEffect hooks, keep only one:
useEffect(() => {
if (isOpen && status === 'authenticated') {
manualFetch();
}
}, [isOpen, status]); // Only this one
3. Fix Redis KEYS Performance Issue ⚠️ CRITICAL
File: lib/services/notifications/notification-service.ts
Line: 293
Problem: redis.keys() blocks Redis and is O(N)
Quick Fix:
// BEFORE (Line 293)
const listKeys = await redis.keys(listKeysPattern);
if (listKeys.length > 0) {
await redis.del(...listKeys);
}
// AFTER (Use SCAN)
const listKeys: string[] = [];
let cursor = '0';
do {
const [nextCursor, keys] = await redis.scan(
cursor,
'MATCH',
listKeysPattern,
'COUNT',
100
);
cursor = nextCursor;
if (keys.length > 0) {
listKeys.push(...keys);
}
} while (cursor !== '0');
if (listKeys.length > 0) {
await redis.del(...listKeys);
}
4. Fix Widget Interval Cleanup ⚠️ HIGH
Files:
components/calendar.tsx(line 70)components/parole.tsx(line 83)components/calendar/calendar-widget.tsx(line 110)
Problem: Intervals may not be cleaned up properly
Quick Fix Pattern:
// BEFORE
useEffect(() => {
fetchEvents();
const intervalId = setInterval(fetchEvents, 300000);
return () => clearInterval(intervalId);
}, []); // ❌ Missing dependencies
// AFTER
useEffect(() => {
if (status !== 'authenticated') return;
const fetchEvents = async () => {
// ... fetch logic
};
fetchEvents(); // Initial fetch
const intervalId = setInterval(fetchEvents, 300000);
return () => {
clearInterval(intervalId);
};
}, [status]); // ✅ Proper dependencies
5. Fix useEffect Infinite Loop Risk ⚠️ HIGH
File: hooks/use-notifications.ts
Line: 255
Problem: Function dependencies cause infinite re-renders
Quick Fix:
// Remove function dependencies, use refs for stable references
const fetchNotificationCountRef = useRef(fetchNotificationCount);
const fetchNotificationsRef = useRef(fetchNotifications);
useEffect(() => {
fetchNotificationCountRef.current = fetchNotificationCount;
fetchNotificationsRef.current = fetchNotifications;
});
useEffect(() => {
if (status !== 'authenticated' || !session?.user) return;
isMountedRef.current = true;
fetchNotificationCountRef.current(true);
fetchNotificationsRef.current();
const intervalId = setInterval(() => {
if (isMountedRef.current) {
fetchNotificationCountRef.current();
}
}, POLLING_INTERVAL);
return () => {
isMountedRef.current = false;
clearInterval(intervalId);
};
}, [status, session?.user?.id]); // ✅ Only primitive values
🔧 Additional Quick Wins
6. Add Request Deduplication Utility
Create: lib/utils/request-deduplication.ts
const pendingRequests = new Map<string, Promise<any>>();
export function deduplicateRequest<T>(
key: string,
requestFn: () => Promise<T>
): Promise<T> {
if (pendingRequests.has(key)) {
return pendingRequests.get(key)!;
}
const promise = requestFn().finally(() => {
pendingRequests.delete(key);
});
pendingRequests.set(key, promise);
return promise;
}
Usage:
const data = await deduplicateRequest(
`notifications-${userId}`,
() => fetch('/api/notifications').then(r => r.json())
);
7. Extract Magic Numbers to Constants
Create: lib/constants/intervals.ts
export const INTERVALS = {
NOTIFICATION_POLLING: 60000, // 1 minute
CALENDAR_REFRESH: 300000, // 5 minutes
PAROLE_POLLING: 30000, // 30 seconds
MIN_FETCH_INTERVAL: 5000, // 5 seconds
FETCH_COOLDOWN: 1000, // 1 second
} as const;
8. Add Error Retry Logic
Create: lib/utils/retry.ts
export async function retry<T>(
fn: () => Promise<T>,
maxAttempts = 3,
delay = 1000
): Promise<T> {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await fn();
} catch (error) {
if (attempt === maxAttempts) throw error;
await new Promise(resolve => setTimeout(resolve, delay * attempt));
}
}
throw new Error('Max retry attempts reached');
}
📋 Testing Checklist
After applying fixes, test:
- No memory leaks (check browser DevTools Memory tab)
- No duplicate API calls (check Network tab)
- Intervals are cleaned up (check console for errors)
- No infinite loops (check React DevTools Profiler)
- Redis performance (check response times)
- Error handling works (test with network offline)
🎯 Priority Order
- Fix 1 (Memory Leak) - Do immediately
- Fix 2 (Double Fetching) - Do immediately
- Fix 3 (Redis KEYS) - Do immediately
- Fix 4 (Widget Cleanup) - Do within 24 hours
- Fix 5 (Infinite Loop) - Do within 24 hours
- Quick Wins - Do within 1 week
Last Updated: Critical fixes quick reference