308 lines
6.7 KiB
Markdown
308 lines
6.7 KiB
Markdown
# 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**:
|
|
```typescript
|
|
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**:
|
|
```typescript
|
|
// 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**:
|
|
```typescript
|
|
// 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**:
|
|
```typescript
|
|
// 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**:
|
|
```typescript
|
|
// 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`
|
|
|
|
```typescript
|
|
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**:
|
|
```typescript
|
|
const data = await deduplicateRequest(
|
|
`notifications-${userId}`,
|
|
() => fetch('/api/notifications').then(r => r.json())
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
### 7. Extract Magic Numbers to Constants
|
|
|
|
**Create**: `lib/constants/intervals.ts`
|
|
|
|
```typescript
|
|
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`
|
|
|
|
```typescript
|
|
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
|
|
|
|
1. **Fix 1** (Memory Leak) - Do immediately
|
|
2. **Fix 2** (Double Fetching) - Do immediately
|
|
3. **Fix 3** (Redis KEYS) - Do immediately
|
|
4. **Fix 4** (Widget Cleanup) - Do within 24 hours
|
|
5. **Fix 5** (Infinite Loop) - Do within 24 hours
|
|
6. **Quick Wins** - Do within 1 week
|
|
|
|
---
|
|
|
|
*Last Updated: Critical fixes quick reference*
|