fix: [공통] SearchableSelectionModal 테이블 HTML 유효성 에러 수정

- renderItem이 <tr>을 반환할 때 <div>로 래핑하여 발생하던 hydration 에러 해결
- cloneElement로 key/onClick을 직접 주입하여 <tbody><div><tr> 구조 방지
- 영향범위: OrderSelectModal, SalesOrderSelectModal, TaxInvoiceIssuance
This commit is contained in:
2026-03-05 21:59:44 +09:00
parent 10c6e20db4
commit 45ad99cb38

View File

@@ -1,6 +1,6 @@
'use client';
import { useState, useCallback, useEffect } from 'react';
import { useState, useCallback, useEffect, cloneElement, isValidElement } from 'react';
import { Search, X, Loader2 } from 'lucide-react';
import {
@@ -156,11 +156,34 @@ export function SearchableSelectionModal<T>(props: SearchableSelectionModalProps
);
}
const itemElements = items.map((item) => (
<div key={keyExtractor(item)} onClick={() => handleItemClick(item)} className="cursor-pointer">
{renderItem(item, isSelected(item))}
</div>
));
const itemElements = items.map((item) => {
const key = keyExtractor(item);
const rendered = renderItem(item, isSelected(item));
// renderItem이 유효한 React 엘리먼트를 반환하면 key와 onClick을 직접 주입 (div 래핑 없이)
// 이렇게 하면 <TableRow> 등 테이블 요소를 <div>로 감싸는 HTML 유효성 에러를 방지
if (isValidElement(rendered)) {
return cloneElement(rendered as React.ReactElement<Record<string, unknown>>, {
key,
onClick: (e: React.MouseEvent) => {
// 기존 onClick이 있으면 먼저 호출
const existingOnClick = (rendered.props as Record<string, unknown>)?.onClick;
if (typeof existingOnClick === 'function') {
(existingOnClick as (e: React.MouseEvent) => void)(e);
}
handleItemClick(item);
},
className: `cursor-pointer ${(rendered.props as Record<string, unknown>)?.className || ''}`.trim(),
});
}
// 일반 텍스트/fragment인 경우 기존 div 래핑 유지
return (
<div key={key} onClick={() => handleItemClick(item)} className="cursor-pointer">
{rendered}
</div>
);
});
if (listWrapper) {
const selectState = mode === 'multiple'