From 214139f4d51493749c8cdc241f7301b5105d572b Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Fri, 6 Feb 2026 18:28:08 -0600 Subject: [PATCH] fix(CQ-WEB-8): Add React.memo to performance-sensitive components Wrap 7 list-item/card components with React.memo to prevent unnecessary re-renders when parent components update but props remain unchanged: - TaskItem (task lists) - EventCard (calendar views) - EntryCard (knowledge base) - WorkspaceCard (workspace list) - TeamCard (team list) - DomainItem (domain list) - ConnectionCard (federation connections) All are pure components rendered inside .map() loops that depend solely on their props for rendering output. Co-Authored-By: Claude Opus 4.6 --- apps/web/src/components/calendar/EventCard.tsx | 7 +++++-- apps/web/src/components/domains/DomainItem.tsx | 9 +++++++-- apps/web/src/components/federation/ConnectionCard.tsx | 5 +++-- apps/web/src/components/knowledge/EntryCard.tsx | 7 +++++-- apps/web/src/components/tasks/TaskItem.tsx | 5 +++-- apps/web/src/components/team/TeamCard.tsx | 8 ++++++-- apps/web/src/components/workspace/WorkspaceCard.tsx | 5 +++-- 7 files changed, 32 insertions(+), 14 deletions(-) diff --git a/apps/web/src/components/calendar/EventCard.tsx b/apps/web/src/components/calendar/EventCard.tsx index 3f76631..86c7909 100644 --- a/apps/web/src/components/calendar/EventCard.tsx +++ b/apps/web/src/components/calendar/EventCard.tsx @@ -1,3 +1,4 @@ +import React from "react"; import type { Event } from "@mosaic/shared"; import { formatTime } from "@/lib/utils/date-format"; @@ -5,7 +6,9 @@ interface EventCardProps { event: Event; } -export function EventCard({ event }: EventCardProps): React.JSX.Element { +export const EventCard = React.memo(function EventCard({ + event, +}: EventCardProps): React.JSX.Element { return (
@@ -23,4 +26,4 @@ export function EventCard({ event }: EventCardProps): React.JSX.Element { {event.location &&

📍 {event.location}

}
); -} +}); diff --git a/apps/web/src/components/domains/DomainItem.tsx b/apps/web/src/components/domains/DomainItem.tsx index eb5e274..580bddf 100644 --- a/apps/web/src/components/domains/DomainItem.tsx +++ b/apps/web/src/components/domains/DomainItem.tsx @@ -1,5 +1,6 @@ "use client"; +import React from "react"; import type { Domain } from "@mosaic/shared"; interface DomainItemProps { @@ -8,7 +9,11 @@ interface DomainItemProps { onDelete?: (domain: Domain) => void; } -export function DomainItem({ domain, onEdit, onDelete }: DomainItemProps): React.ReactElement { +export const DomainItem = React.memo(function DomainItem({ + domain, + onEdit, + onDelete, +}: DomainItemProps): React.ReactElement { return (
@@ -52,4 +57,4 @@ export function DomainItem({ domain, onEdit, onDelete }: DomainItemProps): React
); -} +}); diff --git a/apps/web/src/components/federation/ConnectionCard.tsx b/apps/web/src/components/federation/ConnectionCard.tsx index a60ccc8..75cbb1e 100644 --- a/apps/web/src/components/federation/ConnectionCard.tsx +++ b/apps/web/src/components/federation/ConnectionCard.tsx @@ -3,6 +3,7 @@ * Displays a single federation connection with PDA-friendly design */ +import React from "react"; import { FederationConnectionStatus, type ConnectionDetails } from "@/lib/api/federation"; interface ConnectionCardProps { @@ -50,7 +51,7 @@ function getStatusDisplay(status: FederationConnectionStatus): { } } -export function ConnectionCard({ +export const ConnectionCard = React.memo(function ConnectionCard({ connection, onAccept, onReject, @@ -149,4 +150,4 @@ export function ConnectionCard({ )}
); -} +}); diff --git a/apps/web/src/components/knowledge/EntryCard.tsx b/apps/web/src/components/knowledge/EntryCard.tsx index 036b822..884a6d8 100644 --- a/apps/web/src/components/knowledge/EntryCard.tsx +++ b/apps/web/src/components/knowledge/EntryCard.tsx @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/no-unnecessary-condition */ +import React from "react"; import type { KnowledgeEntryWithTags } from "@mosaic/shared"; import { EntryStatus } from "@mosaic/shared"; import Link from "next/link"; @@ -32,7 +33,9 @@ const visibilityIcons = { PUBLIC: , }; -export function EntryCard({ entry }: EntryCardProps): React.JSX.Element { +export const EntryCard = React.memo(function EntryCard({ + entry, +}: EntryCardProps): React.JSX.Element { const statusInfo = statusConfig[entry.status]; const visibilityIcon = visibilityIcons[entry.visibility]; @@ -107,4 +110,4 @@ export function EntryCard({ entry }: EntryCardProps): React.JSX.Element { ); -} +}); diff --git a/apps/web/src/components/tasks/TaskItem.tsx b/apps/web/src/components/tasks/TaskItem.tsx index 31faec3..24bb5a4 100644 --- a/apps/web/src/components/tasks/TaskItem.tsx +++ b/apps/web/src/components/tasks/TaskItem.tsx @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/no-unnecessary-condition */ +import React from "react"; import type { Task } from "@mosaic/shared"; import { TaskStatus, TaskPriority } from "@mosaic/shared"; import { formatDate, isPastTarget, isApproachingTarget } from "@/lib/utils/date-format"; @@ -21,7 +22,7 @@ const priorityLabels: Record = { [TaskPriority.LOW]: "Low priority", }; -export function TaskItem({ task }: TaskItemProps): React.JSX.Element { +export const TaskItem = React.memo(function TaskItem({ task }: TaskItemProps): React.JSX.Element { const statusIcon = statusIcons[task.status]; const priorityLabel = priorityLabels[task.priority]; @@ -61,4 +62,4 @@ export function TaskItem({ task }: TaskItemProps): React.JSX.Element { ); -} +}); diff --git a/apps/web/src/components/team/TeamCard.tsx b/apps/web/src/components/team/TeamCard.tsx index 98f7e04..b72b43f 100644 --- a/apps/web/src/components/team/TeamCard.tsx +++ b/apps/web/src/components/team/TeamCard.tsx @@ -1,3 +1,4 @@ +import React from "react"; import type { Team } from "@mosaic/shared"; import { Card, CardHeader, CardContent } from "@mosaic/ui"; import Link from "next/link"; @@ -7,7 +8,10 @@ interface TeamCardProps { workspaceId: string; } -export function TeamCard({ team, workspaceId }: TeamCardProps): React.JSX.Element { +export const TeamCard = React.memo(function TeamCard({ + team, + workspaceId, +}: TeamCardProps): React.JSX.Element { return ( @@ -27,4 +31,4 @@ export function TeamCard({ team, workspaceId }: TeamCardProps): React.JSX.Elemen ); -} +}); diff --git a/apps/web/src/components/workspace/WorkspaceCard.tsx b/apps/web/src/components/workspace/WorkspaceCard.tsx index 89adc30..74377a8 100644 --- a/apps/web/src/components/workspace/WorkspaceCard.tsx +++ b/apps/web/src/components/workspace/WorkspaceCard.tsx @@ -1,3 +1,4 @@ +import React from "react"; import type { Workspace } from "@mosaic/shared"; import { WorkspaceMemberRole } from "@mosaic/shared"; import Link from "next/link"; @@ -22,7 +23,7 @@ const roleLabels: Record = { [WorkspaceMemberRole.GUEST]: "Guest", }; -export function WorkspaceCard({ +export const WorkspaceCard = React.memo(function WorkspaceCard({ workspace, userRole, memberCount, @@ -58,4 +59,4 @@ export function WorkspaceCard({ ); -} +});