Skip to content
Merged
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
22 changes: 17 additions & 5 deletions frontend/src/components/SommelierCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ interface SommelierCardProps {
}

// Format feedback: split into paragraphs
function formatFeedback(text: string): React.ReactNode[] {
// Uses stable keys based on content hash to prevent React DOM reconciliation errors
function formatFeedback(text: string): React.ReactNode {
// Split into sentences - but only at sentence boundaries
// A sentence ends with .!? followed by space and uppercase letter
// This avoids breaking "Next.js", "e.g.", "i.e.", etc.
Expand All @@ -39,9 +40,16 @@ function formatFeedback(text: string): React.ReactNode[] {
}
});

return paragraphs.map((p, idx) => (
<p key={idx} className={idx > 0 ? 'mt-3' : ''}>{p}</p>
));
// Wrap in a single container to avoid DOM reconciliation issues when toggling expansion
// This prevents the "insertBefore" error caused by React trying to reconcile
// multiple paragraph elements when the parent's className changes
return (
<div className="space-y-3">
{paragraphs.map((p, idx) => (
<p key={`feedback-${idx}-${p.slice(0, 20).replace(/\s/g, '')}`}>{p}</p>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Since the paragraphs array is deterministically generated from the feedback prop (which is likely static for a given SommelierCard instance), the idx (index) itself provides a stable and unique key for each paragraph within this list. The addition of p.slice(0, 20).replace(/\s/g, '') to the key adds unnecessary complexity without significantly improving stability in this specific scenario, as the order and content of paragraphs for a given feedback string are constant. Simplifying the key to just idx would be more concise and equally effective.

Suggested change
<p key={`feedback-${idx}-${p.slice(0, 20).replace(/\s/g, '')}`}>{p}</p>
<p key={idx}>{p}</p>

))}
</div>
);
}

export function SommelierCard({
Expand Down Expand Up @@ -102,7 +110,11 @@ export function SommelierCard({

{/* Feedback content */}
<div className="p-5">
<div className={`text-gray-700 leading-relaxed ${isExpanded ? '' : 'line-clamp-6'}`}>
<div
className={`text-gray-700 leading-relaxed transition-[max-height] duration-300 ease-in-out overflow-hidden ${
isExpanded ? 'max-h-[2000px]' : 'max-h-36'
}`}
>
{formatFeedback(feedback)}
</div>

Expand Down