fix(CQ-WEB-8): Add React.memo to performance-sensitive components
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
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 <noreply@anthropic.com>
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import React from "react";
|
||||||
import type { Event } from "@mosaic/shared";
|
import type { Event } from "@mosaic/shared";
|
||||||
import { formatTime } from "@/lib/utils/date-format";
|
import { formatTime } from "@/lib/utils/date-format";
|
||||||
|
|
||||||
@@ -5,7 +6,9 @@ interface EventCardProps {
|
|||||||
event: Event;
|
event: Event;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EventCard({ event }: EventCardProps): React.JSX.Element {
|
export const EventCard = React.memo(function EventCard({
|
||||||
|
event,
|
||||||
|
}: EventCardProps): React.JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div className="bg-white p-3 rounded-lg border-l-4 border-blue-500 shadow-sm hover:shadow-md transition-shadow">
|
<div className="bg-white p-3 rounded-lg border-l-4 border-blue-500 shadow-sm hover:shadow-md transition-shadow">
|
||||||
<div className="flex justify-between items-start mb-1">
|
<div className="flex justify-between items-start mb-1">
|
||||||
@@ -23,4 +26,4 @@ export function EventCard({ event }: EventCardProps): React.JSX.Element {
|
|||||||
{event.location && <p className="text-xs text-gray-500">📍 {event.location}</p>}
|
{event.location && <p className="text-xs text-gray-500">📍 {event.location}</p>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
import type { Domain } from "@mosaic/shared";
|
import type { Domain } from "@mosaic/shared";
|
||||||
|
|
||||||
interface DomainItemProps {
|
interface DomainItemProps {
|
||||||
@@ -8,7 +9,11 @@ interface DomainItemProps {
|
|||||||
onDelete?: (domain: Domain) => void;
|
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 (
|
return (
|
||||||
<div className="border rounded-lg p-4 hover:shadow-md transition-shadow">
|
<div className="border rounded-lg p-4 hover:shadow-md transition-shadow">
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between">
|
||||||
@@ -52,4 +57,4 @@ export function DomainItem({ domain, onEdit, onDelete }: DomainItemProps): React
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
* Displays a single federation connection with PDA-friendly design
|
* Displays a single federation connection with PDA-friendly design
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
import { FederationConnectionStatus, type ConnectionDetails } from "@/lib/api/federation";
|
import { FederationConnectionStatus, type ConnectionDetails } from "@/lib/api/federation";
|
||||||
|
|
||||||
interface ConnectionCardProps {
|
interface ConnectionCardProps {
|
||||||
@@ -50,7 +51,7 @@ function getStatusDisplay(status: FederationConnectionStatus): {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ConnectionCard({
|
export const ConnectionCard = React.memo(function ConnectionCard({
|
||||||
connection,
|
connection,
|
||||||
onAccept,
|
onAccept,
|
||||||
onReject,
|
onReject,
|
||||||
@@ -149,4 +150,4 @@ export function ConnectionCard({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
||||||
|
import React from "react";
|
||||||
import type { KnowledgeEntryWithTags } from "@mosaic/shared";
|
import type { KnowledgeEntryWithTags } from "@mosaic/shared";
|
||||||
import { EntryStatus } from "@mosaic/shared";
|
import { EntryStatus } from "@mosaic/shared";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
@@ -32,7 +33,9 @@ const visibilityIcons = {
|
|||||||
PUBLIC: <Eye className="w-3 h-3" />,
|
PUBLIC: <Eye className="w-3 h-3" />,
|
||||||
};
|
};
|
||||||
|
|
||||||
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 statusInfo = statusConfig[entry.status];
|
||||||
const visibilityIcon = visibilityIcons[entry.visibility];
|
const visibilityIcon = visibilityIcons[entry.visibility];
|
||||||
|
|
||||||
@@ -107,4 +110,4 @@ export function EntryCard({ entry }: EntryCardProps): React.JSX.Element {
|
|||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
||||||
|
import React from "react";
|
||||||
import type { Task } from "@mosaic/shared";
|
import type { Task } from "@mosaic/shared";
|
||||||
import { TaskStatus, TaskPriority } from "@mosaic/shared";
|
import { TaskStatus, TaskPriority } from "@mosaic/shared";
|
||||||
import { formatDate, isPastTarget, isApproachingTarget } from "@/lib/utils/date-format";
|
import { formatDate, isPastTarget, isApproachingTarget } from "@/lib/utils/date-format";
|
||||||
@@ -21,7 +22,7 @@ const priorityLabels: Record<TaskPriority, string> = {
|
|||||||
[TaskPriority.LOW]: "Low priority",
|
[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 statusIcon = statusIcons[task.status];
|
||||||
const priorityLabel = priorityLabels[task.priority];
|
const priorityLabel = priorityLabels[task.priority];
|
||||||
|
|
||||||
@@ -61,4 +62,4 @@ export function TaskItem({ task }: TaskItemProps): React.JSX.Element {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import React from "react";
|
||||||
import type { Team } from "@mosaic/shared";
|
import type { Team } from "@mosaic/shared";
|
||||||
import { Card, CardHeader, CardContent } from "@mosaic/ui";
|
import { Card, CardHeader, CardContent } from "@mosaic/ui";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
@@ -7,7 +8,10 @@ interface TeamCardProps {
|
|||||||
workspaceId: string;
|
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 (
|
return (
|
||||||
<Link href={`/settings/workspaces/${workspaceId}/teams/${team.id}`}>
|
<Link href={`/settings/workspaces/${workspaceId}/teams/${team.id}`}>
|
||||||
<Card className="hover:shadow-lg transition-shadow cursor-pointer">
|
<Card className="hover:shadow-lg transition-shadow cursor-pointer">
|
||||||
@@ -27,4 +31,4 @@ export function TeamCard({ team, workspaceId }: TeamCardProps): React.JSX.Elemen
|
|||||||
</Card>
|
</Card>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import React from "react";
|
||||||
import type { Workspace } from "@mosaic/shared";
|
import type { Workspace } from "@mosaic/shared";
|
||||||
import { WorkspaceMemberRole } from "@mosaic/shared";
|
import { WorkspaceMemberRole } from "@mosaic/shared";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
@@ -22,7 +23,7 @@ const roleLabels: Record<WorkspaceMemberRole, string> = {
|
|||||||
[WorkspaceMemberRole.GUEST]: "Guest",
|
[WorkspaceMemberRole.GUEST]: "Guest",
|
||||||
};
|
};
|
||||||
|
|
||||||
export function WorkspaceCard({
|
export const WorkspaceCard = React.memo(function WorkspaceCard({
|
||||||
workspace,
|
workspace,
|
||||||
userRole,
|
userRole,
|
||||||
memberCount,
|
memberCount,
|
||||||
@@ -58,4 +59,4 @@ export function WorkspaceCard({
|
|||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user