⚡ Bolt: 내 작업 목록 useMemo 적용으로 O(N) 재렌더링 방지 - #1303
Conversation
|
👋 Jules, reporting for duty! I'm here to lend a hand with this pull request. When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down. I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job! For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with New to Jules? Learn more at jules.google/docs. For security, I will only act on instructions from the user who triggered this task. |
📝 WalkthroughWalkthrough
ChangesTasks board memoization
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.jules/bolt.md:
- Around line 22-24: Update the “Memoizing My Tasks view inside JSX”
documentation to clarify that memoization skips recalculation only when
filteredTicketTasks and all other dependencies remain unchanged; note that
taskSearch changes filteredTicketTasks and therefore invalidate myTasksBoard. If
the documented example includes a viewMode guard, include viewMode in its
dependency list.
In `@frontend/src/components/TasksLayout.tsx`:
- Around line 311-329: Update the myTasksBoard useMemo callback to return no
board or otherwise avoid mapping filteredTicketTasks when viewMode is not '내
작업'; preserve the existing board rendering for that mode. Since the callback
reads viewMode, include viewMode in the dependency list, or move the board into
a child rendered only for '내 작업'.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 260a65f2-4c80-4264-ae88-06c3b5db7415
📒 Files selected for processing (2)
.jules/bolt.mdfrontend/src/components/TasksLayout.tsx
| ## 2025-02-12 - Memoizing My Tasks view inside JSX | ||
| **Learning:** Inline mapping of arrays inside JSX in large React components causes O(N) recalculation on every render. Although the `kanbanBoard` in `TasksLayout` was properly memoized, the `myTasksBoard` list was still mapped inline in the React return block, causing main thread blocking when other layout state (like search) was updated. | ||
| **Action:** Always verify that *all* conditional array map rendering blocks are properly wrapped in a `useMemo` hook with all necessary external variables and setters in the dependency array (e.g. `[filteredTicketTasks, setSelectedTaskId, setViewMode]`) to prevent state changes in unrelated components from causing a bottleneck. |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Correct the memoization invalidation claim.
taskSearch is a direct input to filteredTicketTasks, so search changes invalidate myTasksBoard. Search is not an unrelated update for this board. State that memoization skips work only when filteredTicketTasks and the other dependencies remain unchanged. If the code adds the viewMode guard, include that dependency in the documented example.
Suggested wording change
- ... when other layout state (like search) was updated.
+ ... when unrelated state changes without changing filteredTicketTasks.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ## 2025-02-12 - Memoizing My Tasks view inside JSX | |
| **Learning:** Inline mapping of arrays inside JSX in large React components causes O(N) recalculation on every render. Although the `kanbanBoard` in `TasksLayout` was properly memoized, the `myTasksBoard` list was still mapped inline in the React return block, causing main thread blocking when other layout state (like search) was updated. | |
| **Action:** Always verify that *all* conditional array map rendering blocks are properly wrapped in a `useMemo` hook with all necessary external variables and setters in the dependency array (e.g. `[filteredTicketTasks, setSelectedTaskId, setViewMode]`) to prevent state changes in unrelated components from causing a bottleneck. | |
| ## 2025-02-12 - Memoizing My Tasks view inside JSX | |
| **Learning:** Inline mapping of arrays inside JSX in large React components causes O(N) recalculation on every render. Although the `kanbanBoard` in `TasksLayout` was properly memoized, the `myTasksBoard` list was still mapped inline in the React return block, causing main thread blocking when unrelated state changes without changing `filteredTicketTasks`. | |
| **Action:** Always verify that *all* conditional array map rendering blocks are properly wrapped in a `useMemo` hook with all necessary external variables and setters in the dependency array (e.g. `[filteredTicketTasks, setSelectedTaskId, setViewMode]`) to prevent state changes in unrelated components from causing a bottleneck. |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.jules/bolt.md around lines 22 - 24, Update the “Memoizing My Tasks view
inside JSX” documentation to clarify that memoization skips recalculation only
when filteredTicketTasks and all other dependencies remain unchanged; note that
taskSearch changes filteredTicketTasks and therefore invalidate myTasksBoard. If
the documented example includes a viewMode guard, include viewMode in its
dependency list.
| const myTasksBoard = useMemo(() => ( | ||
| <div className="space-y-4 max-w-4xl mx-auto"> | ||
| <h2 className="font-bold text-lg mb-4">내 작업</h2> | ||
| {filteredTicketTasks.length > 0 ? filteredTicketTasks.map(task => ( | ||
| <button key={task.id} type="button" className="flex w-full items-center justify-between p-4 rounded-xl border border-border bg-card text-left shadow-sm transition-colors hover:border-primary/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40" onClick={() => { setSelectedTaskId(task.id); setViewMode('작업 상세'); }}> | ||
| <div className="flex items-center gap-4"> | ||
| <div className={`size-3 rounded-full ${task.priority === 'urgent' ? 'bg-red-500' : task.priority === 'high' ? 'bg-orange-500' : 'bg-blue-500'}`}></div> | ||
| <div> | ||
| <h3 className="font-bold text-sm">{safeTaskTitle(task.title)}</h3> | ||
| <p className="text-xs text-muted-foreground mt-1">근거: {getTaskEvidenceLabel(task)} | 원본: {getTaskSourceLabel(task.source_type)}</p> | ||
| </div> | ||
| </div> | ||
| <span className={`px-2 py-1 rounded-full text-xs font-bold ${task.status === 'done' ? 'bg-green-100 text-green-700' : 'bg-secondary text-secondary-foreground'}`}>{taskStatusLabels[task.status]}</span> | ||
| </button> | ||
| )) : ( | ||
| <p className="rounded-xl border border-dashed border-border bg-card p-4 text-sm font-semibold text-muted-foreground">서명 세션에 연결된 내 작업이 없습니다.</p> | ||
| )} | ||
| </div> | ||
| ), [filteredTicketTasks, setSelectedTaskId, setViewMode]); |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win
Guard the memoized board before mapping hidden tasks.
useMemo evaluates this callback when filteredTicketTasks changes, even when viewMode is not '내 작업'. Line 706 only controls whether the already-built element is returned. Search, priority changes, and ticket refreshes therefore allocate an O(N) invisible board.
Guard the callback with viewMode, or isolate the board in a child component rendered only for '내 작업'. Include viewMode in the dependency list when the callback reads it.
Suggested fix
- const myTasksBoard = useMemo(() => (
+ const myTasksBoard = useMemo(() => {
+ if (viewMode !== '내 작업') return null;
+ return (
<div className="space-y-4 max-w-4xl mx-auto">
...
</div>
- ), [filteredTicketTasks, setSelectedTaskId, setViewMode]);
+ );
+ }, [filteredTicketTasks, viewMode, setSelectedTaskId, setViewMode]);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const myTasksBoard = useMemo(() => ( | |
| <div className="space-y-4 max-w-4xl mx-auto"> | |
| <h2 className="font-bold text-lg mb-4">내 작업</h2> | |
| {filteredTicketTasks.length > 0 ? filteredTicketTasks.map(task => ( | |
| <button key={task.id} type="button" className="flex w-full items-center justify-between p-4 rounded-xl border border-border bg-card text-left shadow-sm transition-colors hover:border-primary/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40" onClick={() => { setSelectedTaskId(task.id); setViewMode('작업 상세'); }}> | |
| <div className="flex items-center gap-4"> | |
| <div className={`size-3 rounded-full ${task.priority === 'urgent' ? 'bg-red-500' : task.priority === 'high' ? 'bg-orange-500' : 'bg-blue-500'}`}></div> | |
| <div> | |
| <h3 className="font-bold text-sm">{safeTaskTitle(task.title)}</h3> | |
| <p className="text-xs text-muted-foreground mt-1">근거: {getTaskEvidenceLabel(task)} | 원본: {getTaskSourceLabel(task.source_type)}</p> | |
| </div> | |
| </div> | |
| <span className={`px-2 py-1 rounded-full text-xs font-bold ${task.status === 'done' ? 'bg-green-100 text-green-700' : 'bg-secondary text-secondary-foreground'}`}>{taskStatusLabels[task.status]}</span> | |
| </button> | |
| )) : ( | |
| <p className="rounded-xl border border-dashed border-border bg-card p-4 text-sm font-semibold text-muted-foreground">서명 세션에 연결된 내 작업이 없습니다.</p> | |
| )} | |
| </div> | |
| ), [filteredTicketTasks, setSelectedTaskId, setViewMode]); | |
| const myTasksBoard = useMemo(() => { | |
| if (viewMode !== '내 작업') return null; | |
| return ( | |
| <div className="space-y-4 max-w-4xl mx-auto"> | |
| <h2 className="font-bold text-lg mb-4">내 작업</h2> | |
| {filteredTicketTasks.length > 0 ? filteredTicketTasks.map(task => ( | |
| <button key={task.id} type="button" className="flex w-full items-center justify-between p-4 rounded-xl border border-border bg-card text-left shadow-sm transition-colors hover:border-primary/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40" onClick={() => { setSelectedTaskId(task.id); setViewMode('작업 상세'); }}> | |
| <div className="flex items-center gap-4"> | |
| <div className={`size-3 rounded-full ${task.priority === 'urgent' ? 'bg-red-500' : task.priority === 'high' ? 'bg-orange-500' : 'bg-blue-500'}`}></div> | |
| <div> | |
| <h3 className="font-bold text-sm">{safeTaskTitle(task.title)}</h3> | |
| <p className="text-xs text-muted-foreground mt-1">근거: {getTaskEvidenceLabel(task)} | 원본: {getTaskSourceLabel(task.source_type)}</p> | |
| </div> | |
| </div> | |
| <span className={`px-2 py-1 rounded-full text-xs font-bold ${task.status === 'done' ? 'bg-green-100 text-green-700' : 'bg-secondary text-secondary-foreground'}`}>{taskStatusLabels[task.status]}</span> | |
| </button> | |
| )) : ( | |
| <p className="rounded-xl border border-dashed border-border bg-card p-4 text-sm font-semibold text-muted-foreground">서명 세션에 연결된 내 작업이 없습니다.</p> | |
| )} | |
| </div> | |
| ); | |
| }, [filteredTicketTasks, viewMode, setSelectedTaskId, setViewMode]); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/components/TasksLayout.tsx` around lines 311 - 329, Update the
myTasksBoard useMemo callback to return no board or otherwise avoid mapping
filteredTicketTasks when viewMode is not '내 작업'; preserve the existing board
rendering for that mode. Since the callback reads viewMode, include viewMode in
the dependency list, or move the board into a child rendered only for '내 작업'.
|
PR governance metadata gate is not ready for
|
|
Closing this older lane as a proven semantic duplicate of #1315. Fresh comparison at the current refs found both PRs start from No performance or review evidence is transferred. #1315 must still be rebuilt onto the live protected base, fix its current-head finding, and pass fresh exact-head gates and governance. |
Understood. Acknowledging that this work is now obsolete as a duplicate of #1315 and stopping work on this task. |
💡 무엇을: TasksLayout 컴포넌트 내의 '내 작업' 목록 렌더링 부분을
useMemo로 감쌌습니다.🎯 왜: 관련된 상태 변경이 일어날 때마다
filteredTicketTasks가 다시 매핑되면서 메인 스레드를 블로킹하는 O(N) 렌더링 비용을 방지하기 위함입니다.📊 영향: 상태 변경 시 불필요한 재렌더링을 방지하여 체감 성능이 크게 향상됩니다.
🔬 측정 방법: 내 작업 탭에서 다른 상태(예: 검색) 변경 시 TasksLayout의 프로파일링을 통해 렌더링 시간을 확인합니다.
PR created automatically by Jules for task 2835095134316709789 started by @seonghobae
Summary by CodeRabbit