Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

## 2024-05-30 - [Memoizing List Components in react-window]
**Learning:** Virtualized lists (`react-window`) still re-render internal `Row` components when their parent updates. If the rows are complex or numerous (e.g. `EventList`), rendering them frequently can still cause performance degradation.
**Action:** Use `React.memo` with a custom comparator (e.g. `areEqual` from `react-window`) for rows passed to virtualized lists to stop unnecessary re-renders. Also apply `React.memo` to deeply nested simple row components like `StatListRow` if they are rendered frequently without prop changes.
25 changes: 19 additions & 6 deletions packages/web/src/components/dashboard/event-list.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useMemo } from "react";
import { useMemo, memo } from "react";
import { List, type RowComponentProps } from "react-window";
import { User, Bot, Wrench, ChevronRight } from "lucide-react";
import {
Expand Down Expand Up @@ -149,7 +149,7 @@ type RowViewProps = {
chevron?: "collapsed" | "expanded";
};

function RowView({
const RowView = memo(function RowView({
label,
preview,
time,
Expand Down Expand Up @@ -199,7 +199,7 @@ function RowView({
</span>
</button>
);
}
});

type RowProps = {
rows: FlatRow[];
Expand All @@ -209,7 +209,19 @@ type RowProps = {
onToggleGroup: (firstIdx: number) => void;
};

function Row({
const areEqual = (prevProps: Readonly<RowComponentProps<RowProps>>, nextProps: Readonly<RowComponentProps<RowProps>>) => {
return (
prevProps.index === nextProps.index &&
prevProps.style === nextProps.style &&
prevProps.rows === nextProps.rows &&
prevProps.selectedIdx === nextProps.selectedIdx &&
prevProps.sessionStartedAt === nextProps.sessionStartedAt &&
prevProps.onSelect === nextProps.onSelect &&
prevProps.onToggleGroup === nextProps.onToggleGroup
);
};

const MemoizedRow = memo(function Row({
index,
style,
rows,
Expand Down Expand Up @@ -257,7 +269,7 @@ function Row({
/>
</div>
);
}
}, areEqual);

export function EventList({
events,
Expand All @@ -282,7 +294,8 @@ export function EventList({

return (
<List
rowComponent={Row}
// @ts-expect-error react-window types for rowComponent don't perfectly align with React.memo
rowComponent={MemoizedRow}
rowCount={rows.length}
rowHeight={ROW_HEIGHT}
rowProps={{
Expand Down
6 changes: 3 additions & 3 deletions packages/web/src/components/dashboard/stat-list.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ReactNode } from 'react'
import { memo, type ReactNode } from 'react'
import { cn } from '@/lib/utils'

interface StatListRowProps {
Expand All @@ -16,7 +16,7 @@ const toneStyles: Record<NonNullable<StatListRowProps['tone']>, string> = {
muted: 'bg-muted',
}

export function StatListRow({
export const StatListRow = memo(function StatListRow({
icon,
label,
value,
Expand Down Expand Up @@ -52,7 +52,7 @@ export function StatListRow({
</span>
</div>
)
}
})

export function StatList({
children,
Expand Down
Loading