fix: 컬럼 리사이즈 무한 리렌더링 및 원위치 복귀 문제 해결

- ref callback에서 state 업데이트 제거
- useEffect + setTimeout으로 초기 너비 측정 (한 번만)
- hasInitializedWidths useRef로 중복 측정 방지
- columnRefs useRef로 DOM 직접 참조
- 드래그 중 리렌더링 없이 DOM만 직접 조작
- 부드럽고 정확한 리사이즈 구현 완료
This commit is contained in:
kjs 2025-11-03 12:18:50 +09:00
parent 511884f323
commit 3332c87293
2 changed files with 64 additions and 26 deletions

View File

@ -107,6 +107,8 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
const [total, setTotal] = useState(0); const [total, setTotal] = useState(0);
const [selectedRows, setSelectedRows] = useState<Set<number>>(new Set()); const [selectedRows, setSelectedRows] = useState<Set<number>>(new Set());
const [columnWidths, setColumnWidths] = useState<Record<string, number>>({}); const [columnWidths, setColumnWidths] = useState<Record<string, number>>({});
const hasInitializedWidthsRef = useRef(false);
const columnRefs = useRef<Record<string, HTMLTableCellElement | null>>({});
// SaveModal 상태 (등록/수정 통합) // SaveModal 상태 (등록/수정 통합)
const [showSaveModal, setShowSaveModal] = useState(false); const [showSaveModal, setShowSaveModal] = useState(false);
@ -409,6 +411,35 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
// 페이지 크기 설정 // 페이지 크기 설정
const pageSize = component.pagination?.pageSize || 10; const pageSize = component.pagination?.pageSize || 10;
// 초기 컬럼 너비 측정 (한 번만)
useEffect(() => {
if (!hasInitializedWidthsRef.current && visibleColumns.length > 0) {
// 약간의 지연을 두고 DOM이 완전히 렌더링된 후 측정
const timer = setTimeout(() => {
const newWidths: Record<string, number> = {};
let hasAnyWidth = false;
visibleColumns.forEach((column) => {
const thElement = columnRefs.current[column.id];
if (thElement) {
const measuredWidth = thElement.offsetWidth;
if (measuredWidth > 0) {
newWidths[column.id] = measuredWidth;
hasAnyWidth = true;
}
}
});
if (hasAnyWidth) {
setColumnWidths(newWidths);
hasInitializedWidthsRef.current = true;
}
}, 100);
return () => clearTimeout(timer);
}
}, [visibleColumns]);
// 데이터 로드 함수 // 데이터 로드 함수
const loadData = useCallback( const loadData = useCallback(
async (page: number = 1, searchParams: Record<string, any> = {}) => { async (page: number = 1, searchParams: Record<string, any> = {}) => {
@ -1939,18 +1970,7 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
return ( return (
<TableHead <TableHead
key={column.id} key={column.id}
ref={(el) => { ref={(el) => (columnRefs.current[column.id] = el)}
// 첫 렌더링 시 실제 너비를 측정해서 상태에 저장
if (el && !columnWidth) {
const measuredWidth = el.offsetWidth;
if (measuredWidth > 0) {
setColumnWidths(prev => ({
...prev,
[column.id]: measuredWidth
}));
}
}
}}
className="relative bg-gradient-to-r from-gray-50 to-slate-50 px-4 font-semibold text-gray-700 select-none" className="relative bg-gradient-to-r from-gray-50 to-slate-50 px-4 font-semibold text-gray-700 select-none"
style={{ style={{
width: columnWidth ? `${columnWidth}px` : undefined, width: columnWidth ? `${columnWidth}px` : undefined,
@ -1968,7 +1988,7 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
const thElement = e.currentTarget.parentElement as HTMLTableCellElement; const thElement = columnRefs.current[column.id];
if (!thElement) return; if (!thElement) return;
const startX = e.clientX; const startX = e.clientX;

View File

@ -247,6 +247,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
const [columnWidths, setColumnWidths] = useState<Record<string, number>>({}); const [columnWidths, setColumnWidths] = useState<Record<string, number>>({});
const columnRefs = useRef<Record<string, HTMLTableCellElement | null>>({}); const columnRefs = useRef<Record<string, HTMLTableCellElement | null>>({});
const [isAllSelected, setIsAllSelected] = useState(false); const [isAllSelected, setIsAllSelected] = useState(false);
const hasInitializedWidths = useRef(false);
// 필터 설정 관련 상태 // 필터 설정 관련 상태
const [isFilterSettingOpen, setIsFilterSettingOpen] = useState(false); const [isFilterSettingOpen, setIsFilterSettingOpen] = useState(false);
@ -794,6 +795,35 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
} }
}, [tableConfig.refreshInterval, isDesignMode]); }, [tableConfig.refreshInterval, isDesignMode]);
// 초기 컬럼 너비 측정 (한 번만)
useEffect(() => {
if (!hasInitializedWidths.current && visibleColumns.length > 0) {
// 약간의 지연을 두고 DOM이 완전히 렌더링된 후 측정
const timer = setTimeout(() => {
const newWidths: Record<string, number> = {};
let hasAnyWidth = false;
visibleColumns.forEach((column) => {
const thElement = columnRefs.current[column.columnName];
if (thElement) {
const measuredWidth = thElement.offsetWidth;
if (measuredWidth > 0) {
newWidths[column.columnName] = measuredWidth;
hasAnyWidth = true;
}
}
});
if (hasAnyWidth) {
setColumnWidths(newWidths);
hasInitializedWidths.current = true;
}
}, 100);
return () => clearTimeout(timer);
}
}, [visibleColumns]);
// ======================================== // ========================================
// 페이지네이션 JSX // 페이지네이션 JSX
// ======================================== // ========================================
@ -1027,19 +1057,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
return ( return (
<th <th
key={column.columnName} key={column.columnName}
ref={(el) => { ref={(el) => (columnRefs.current[column.columnName] = el)}
columnRefs.current[column.columnName] = el;
// 첫 렌더링 시 실제 너비를 측정해서 상태에 저장
if (el && !columnWidth) {
const measuredWidth = el.offsetWidth;
if (measuredWidth > 0) {
setColumnWidths(prev => ({
...prev,
[column.columnName]: measuredWidth
}));
}
}
}}
className={cn( className={cn(
"relative h-10 px-2 py-2 text-xs font-semibold text-foreground overflow-hidden text-ellipsis bg-background select-none sm:h-12 sm:px-6 sm:py-3 sm:text-sm sm:whitespace-nowrap", "relative h-10 px-2 py-2 text-xs font-semibold text-foreground overflow-hidden text-ellipsis bg-background select-none sm:h-12 sm:px-6 sm:py-3 sm:text-sm sm:whitespace-nowrap",
column.sortable && "cursor-pointer" column.sortable && "cursor-pointer"