diff --git a/.jules/bolt.md b/.jules/bolt.md index d5fcbd53e..ee4e66261 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -15,3 +15,7 @@ **Learning:** `dict.setdefault(key, []).append(value)` evaluates the empty-list default on every iteration, including when the key already exists. In grouping loops, `defaultdict(list)` avoids those transient unused list allocations while preserving insertion order. **Action:** Use `defaultdict(list)` when missing keys are intentionally initialized with lists. Keep `setdefault` when its eager-default behavior or an ordinary `dict` is part of the required contract, and benchmark before claiming a material end-to-end improvement. +## 2025-02-12 - Wrap array rendering loops with useMemo + +**Learning:** Mapping over arrays of properties inside a React functional component's render execution directly causes O(N) evaluations when unrelated state (like input queries or tab switches) updates the component. +**Action:** Always wrap `.map()` calls that output elements in a `useMemo` block with exact prop array dependencies in large lists, especially if they are passed as props to avoid blocking the main thread on every re-render. diff --git a/frontend/src/components/calendar/CalendarCandidateView.tsx b/frontend/src/components/calendar/CalendarCandidateView.tsx index 35c681e06..7923a2277 100644 --- a/frontend/src/components/calendar/CalendarCandidateView.tsx +++ b/frontend/src/components/calendar/CalendarCandidateView.tsx @@ -1,3 +1,4 @@ +import { useMemo } from 'react'; import type { CalendarCandidateEvent } from './types'; type Props = { @@ -5,22 +6,30 @@ type Props = { }; export function CalendarCandidateView({ visibleCandidateEvents }: Props) { + + // ⚡ Bolt: Wrap candidate events map in useMemo to prevent O(N) re-renders + // 🎯 Why: Mapping over event lists during unrelated parent renders (e.g. state changes) blocks the main thread. + const candidateEventsList = useMemo(() => ( + <> + {visibleCandidateEvents.map((event) => ( +
+

{event.title}

+

{event.source}

+

{event.mode}

+
+ ))} + {visibleCandidateEvents.length === 0 && ( +

+ 표시 중인 캘린더 후보가 없습니다. +

+ )} + + ), [visibleCandidateEvents]); return (

일정 후보

- {visibleCandidateEvents.map((event) => ( -
-

{event.title}

-

{event.source}

-

{event.mode}

-
- ))} - {visibleCandidateEvents.length === 0 && ( -

- 표시 중인 캘린더 후보가 없습니다. -

- )} + {candidateEventsList}
); diff --git a/frontend/src/components/calendar/CalendarWeekView.tsx b/frontend/src/components/calendar/CalendarWeekView.tsx index b49d118e6..ea102de55 100644 --- a/frontend/src/components/calendar/CalendarWeekView.tsx +++ b/frontend/src/components/calendar/CalendarWeekView.tsx @@ -1,3 +1,4 @@ +import { useMemo } from 'react'; import type { CalendarWeekEvent } from './types'; type Props = { @@ -5,22 +6,30 @@ type Props = { }; export function CalendarWeekView({ visibleWeekEvents }: Props) { + + // ⚡ Bolt: Wrap week events map in useMemo to prevent O(N) re-renders + // 🎯 Why: Mapping over event lists during unrelated parent renders (e.g. state changes) blocks the main thread. + const weekEventsList = useMemo(() => ( + <> + {visibleWeekEvents.map((event) => ( +
+

{event.day}

+

{event.title}

+

{event.source}

+
+ ))} + {visibleWeekEvents.length === 0 && ( +

+ 표시 중인 캘린더 일정이 없습니다. +

+ )} + + ), [visibleWeekEvents]); return (

주간 캘린더

- {visibleWeekEvents.map((event) => ( -
-

{event.day}

-

{event.title}

-

{event.source}

-
- ))} - {visibleWeekEvents.length === 0 && ( -

- 표시 중인 캘린더 일정이 없습니다. -

- )} + {weekEventsList}
);