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:
parent
4f3e9ec19e
commit
73e3d56381
|
|
@ -111,19 +111,10 @@ export function PopDashboardComponent({
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const [containerWidth, setContainerWidth] = useState(300);
|
const [containerWidth, setContainerWidth] = useState(300);
|
||||||
|
|
||||||
// 빈 설정
|
// 보이는 아이템만 필터링 (hooks 이전에 early return 불가하므로 빈 배열 허용)
|
||||||
if (!config || !config.items.length) {
|
const visibleItems = Array.isArray(config?.items)
|
||||||
return (
|
? config.items.filter((item) => item.visible)
|
||||||
<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);
|
|
||||||
|
|
||||||
// 컨테이너 크기 감지
|
// 컨테이너 크기 감지
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -140,6 +131,7 @@ export function PopDashboardComponent({
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 데이터 로딩 함수
|
// 데이터 로딩 함수
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
const fetchAllData = useCallback(async () => {
|
const fetchAllData = useCallback(async () => {
|
||||||
if (!visibleItems.length) {
|
if (!visibleItems.length) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|
@ -165,7 +157,6 @@ export function PopDashboardComponent({
|
||||||
|
|
||||||
setDataMap(newDataMap);
|
setDataMap(newDataMap);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [JSON.stringify(visibleItems.map((i) => i.id))]);
|
}, [JSON.stringify(visibleItems.map((i) => i.id))]);
|
||||||
|
|
||||||
// 초기 로딩 + 주기적 새로고침
|
// 초기 로딩 + 주기적 새로고침
|
||||||
|
|
@ -186,6 +177,17 @@ export function PopDashboardComponent({
|
||||||
};
|
};
|
||||||
}, [fetchAllData, visibleItems]);
|
}, [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 renderSingleItem = (item: DashboardItem) => {
|
||||||
const itemData = dataMap[item.id];
|
const itemData = dataMap[item.id];
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ import { validateExpression } from "./utils/formula";
|
||||||
|
|
||||||
interface ConfigPanelProps {
|
interface ConfigPanelProps {
|
||||||
config: PopDashboardConfig | undefined;
|
config: PopDashboardConfig | undefined;
|
||||||
onChange: (config: PopDashboardConfig) => void;
|
onUpdate: (config: PopDashboardConfig) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== 기본값 =====
|
// ===== 기본값 =====
|
||||||
|
|
@ -806,9 +806,10 @@ function GridLayoutEditor({
|
||||||
|
|
||||||
export function PopDashboardConfigPanel({
|
export function PopDashboardConfigPanel({
|
||||||
config,
|
config,
|
||||||
onChange,
|
onUpdate: onChange,
|
||||||
}: ConfigPanelProps) {
|
}: ConfigPanelProps) {
|
||||||
const cfg = config ?? DEFAULT_CONFIG;
|
// config가 빈 객체 {}로 전달될 수 있으므로 spread로 기본값 보장
|
||||||
|
const cfg: PopDashboardConfig = { ...DEFAULT_CONFIG, ...(config || {}) };
|
||||||
const [activeTab, setActiveTab] = useState<"basic" | "items" | "layout">(
|
const [activeTab, setActiveTab] = useState<"basic" | "items" | "layout">(
|
||||||
"basic"
|
"basic"
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,8 @@ export function PopDashboardPreviewComponent({
|
||||||
}: {
|
}: {
|
||||||
config?: PopDashboardConfig;
|
config?: PopDashboardConfig;
|
||||||
}) {
|
}) {
|
||||||
if (!config || !config.items.length) {
|
// config가 빈 객체 {} 또는 items가 없는 경우 방어
|
||||||
|
if (!config || !Array.isArray(config.items) || !config.items.length) {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full flex-col items-center justify-center gap-1 overflow-hidden">
|
<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" />
|
<BarChart3 className="h-6 w-6 text-muted-foreground/50" />
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue