fix(pop-dashboard): React Hooks 규칙 위반 수정 + ConfigPanel props 정합성 + 방어 코드 강화

- PopDashboardComponent: early return을 모든 hooks 이후로 이동 (Rules of Hooks)
- PopDashboardConfigPanel: onChange -> onUpdate prop 이름 정합, 빈 객체 config 방어
- PopDashboardPreview: Array.isArray 방어 추가

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
SeongHyun Kim 2026-02-10 12:20:44 +09:00
parent 4f3e9ec19e
commit 73e3d56381
3 changed files with 22 additions and 18 deletions

View File

@ -111,19 +111,10 @@ export function PopDashboardComponent({
const containerRef = useRef<HTMLDivElement>(null);
const [containerWidth, setContainerWidth] = useState(300);
// 빈 설정
if (!config || !config.items.length) {
return (
<div className="flex h-full w-full items-center justify-center bg-muted/20">
<span className="text-sm text-muted-foreground">
</span>
</div>
);
}
// 보이는 아이템만 필터링
const visibleItems = config.items.filter((item) => item.visible);
// 보이는 아이템만 필터링 (hooks 이전에 early return 불가하므로 빈 배열 허용)
const visibleItems = Array.isArray(config?.items)
? config.items.filter((item) => item.visible)
: [];
// 컨테이너 크기 감지
useEffect(() => {
@ -140,6 +131,7 @@ export function PopDashboardComponent({
}, []);
// 데이터 로딩 함수
// eslint-disable-next-line react-hooks/exhaustive-deps
const fetchAllData = useCallback(async () => {
if (!visibleItems.length) {
setLoading(false);
@ -165,7 +157,6 @@ export function PopDashboardComponent({
setDataMap(newDataMap);
setLoading(false);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [JSON.stringify(visibleItems.map((i) => i.id))]);
// 초기 로딩 + 주기적 새로고침
@ -186,6 +177,17 @@ export function PopDashboardComponent({
};
}, [fetchAllData, visibleItems]);
// 빈 설정 (모든 hooks 이후에 early return)
if (!config || !config.items?.length) {
return (
<div className="flex h-full w-full items-center justify-center bg-muted/20">
<span className="text-sm text-muted-foreground">
</span>
</div>
);
}
// 단일 아이템 렌더링
const renderSingleItem = (item: DashboardItem) => {
const itemData = dataMap[item.id];

View File

@ -45,7 +45,7 @@ import { validateExpression } from "./utils/formula";
interface ConfigPanelProps {
config: PopDashboardConfig | undefined;
onChange: (config: PopDashboardConfig) => void;
onUpdate: (config: PopDashboardConfig) => void;
}
// ===== 기본값 =====
@ -806,9 +806,10 @@ function GridLayoutEditor({
export function PopDashboardConfigPanel({
config,
onChange,
onUpdate: onChange,
}: ConfigPanelProps) {
const cfg = config ?? DEFAULT_CONFIG;
// config가 빈 객체 {}로 전달될 수 있으므로 spread로 기본값 보장
const cfg: PopDashboardConfig = { ...DEFAULT_CONFIG, ...(config || {}) };
const [activeTab, setActiveTab] = useState<"basic" | "items" | "layout">(
"basic"
);

View File

@ -64,7 +64,8 @@ export function PopDashboardPreviewComponent({
}: {
config?: PopDashboardConfig;
}) {
if (!config || !config.items.length) {
// config가 빈 객체 {} 또는 items가 없는 경우 방어
if (!config || !Array.isArray(config.items) || !config.items.length) {
return (
<div className="flex h-full w-full flex-col items-center justify-center gap-1 overflow-hidden">
<BarChart3 className="h-6 w-6 text-muted-foreground/50" />