Problem
Calendar views expand recurring events by iterating day-by-day from start_date to end_date, creating a new Date object for each day. This happens on every render in components that don't memoize the transformation.
Affected files
- `sroapp/src/pages/Dashboard.jsx` (lines 102-124) — NOT memoized
- `sroapp/src/components/ui/UnifiedActivitiesCalendar.jsx` (lines 83-124) — NOT memoized
- `sroapp/src/pages/admin/AdminPanel.jsx` (lines 159-178) — already wrapped in `useMemo` ✅
The expensive loop
```javascript
for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
const dayName = d.toLocaleDateString('en-US', { weekday: 'long' });
if (recurringDays[dayName]) {
events.push({
...activity,
date: new Date(d).toISOString().split('T')[0],
// ... more fields
});
}
}
```
Impact
- A recurring event spanning 6 months (180 days) creates 180 Date objects + 180 string conversions
- With 100 recurring activities: 18,000+ Date objects created per render
- On old phones (the target demographic — UP students with budget devices), this causes visible lag when navigating to the dashboard or calendar page
- The loop also mutates the Date object in-place (`d.setDate()`), which is a subtle correctness risk
Proposed fix
Wrap the event expansion in `useMemo` in both `Dashboard.jsx` and `UnifiedActivitiesCalendar.jsx`, keyed on the activities data array.
Dashboard.jsx
The `fetchActivities` effect already stores the result in state. The event expansion should be a `useMemo` that depends on the activities state, not computed inline during the effect.
UnifiedActivitiesCalendar.jsx
Same approach — the component receives activities as a prop, so the expansion should be:
```javascript
const expandedEvents = useMemo(() => {
return activities.flatMap(activity => expandRecurring(activity));
}, [activities]);
```
Optionally, replace the day-by-day loop with `date-fns/eachDayOfInterval` for cleaner code and better performance:
```javascript
import { eachDayOfInterval, format, getDay } from 'date-fns';
const days = eachDayOfInterval({ start, end });
const matchingDays = days.filter(d => recurringDays[format(d, 'EEEE')]);
```
Why we deferred this
The fix requires refactoring the event transformation logic in 2 files. `Dashboard.jsx` mixes the expansion with data fetching in a `useEffect`, so it needs to be separated into a `useMemo` that runs after the data is stored in state. `UnifiedActivitiesCalendar.jsx` has a similar structure.
The `AdminPanel.jsx` version (the most-used admin view) is already memoized, so the impact is primarily on the student Dashboard and the shared calendar component. These pages are less frequently loaded than the admin dashboard.
Not a breaking issue — just causes slower renders on low-end devices with many recurring activities.
Problem
Calendar views expand recurring events by iterating day-by-day from start_date to end_date, creating a new Date object for each day. This happens on every render in components that don't memoize the transformation.
Affected files
The expensive loop
```javascript
for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
const dayName = d.toLocaleDateString('en-US', { weekday: 'long' });
if (recurringDays[dayName]) {
events.push({
...activity,
date: new Date(d).toISOString().split('T')[0],
// ... more fields
});
}
}
```
Impact
Proposed fix
Wrap the event expansion in `useMemo` in both `Dashboard.jsx` and `UnifiedActivitiesCalendar.jsx`, keyed on the activities data array.
Dashboard.jsx
The `fetchActivities` effect already stores the result in state. The event expansion should be a `useMemo` that depends on the activities state, not computed inline during the effect.
UnifiedActivitiesCalendar.jsx
Same approach — the component receives activities as a prop, so the expansion should be:
```javascript
const expandedEvents = useMemo(() => {
return activities.flatMap(activity => expandRecurring(activity));
}, [activities]);
```
Optionally, replace the day-by-day loop with `date-fns/eachDayOfInterval` for cleaner code and better performance:
```javascript
import { eachDayOfInterval, format, getDay } from 'date-fns';
const days = eachDayOfInterval({ start, end });
const matchingDays = days.filter(d => recurringDays[format(d, 'EEEE')]);
```
Why we deferred this
The fix requires refactoring the event transformation logic in 2 files. `Dashboard.jsx` mixes the expansion with data fetching in a `useEffect`, so it needs to be separated into a `useMemo` that runs after the data is stored in state. `UnifiedActivitiesCalendar.jsx` has a similar structure.
The `AdminPanel.jsx` version (the most-used admin view) is already memoized, so the impact is primarily on the student Dashboard and the shared calendar component. These pages are less frequently loaded than the admin dashboard.
Not a breaking issue — just causes slower renders on low-end devices with many recurring activities.