From 9b9578206bc1f12133d289cb8cf8501d2102f36c Mon Sep 17 00:00:00 2001 From: Jason Date: Wed, 20 May 2026 10:55:12 +0800 Subject: [PATCH] fix: prevent infinite render loop in Codex model catalog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Break bidirectional sync cycle between catalogRows (child) and catalogModels (parent): - Remove catalogModels from child→parent effect dependencies - Track last sent data in ref to avoid redundant callbacks - Sync ref when parent pushes new data to prevent false positives Fixes severe UI jittering when adding/editing catalog entries. --- src/components/providers/forms/CodexFormFields.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/components/providers/forms/CodexFormFields.tsx b/src/components/providers/forms/CodexFormFields.tsx index 1ee22c299..21eb26700 100644 --- a/src/components/providers/forms/CodexFormFields.tsx +++ b/src/components/providers/forms/CodexFormFields.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { Button } from "@/components/ui/button"; import { FormLabel } from "@/components/ui/form"; @@ -123,6 +123,9 @@ export function CodexFormFields({ catalogModels.map((m) => createCatalogRow(m)), ); + // 记录上次发送给父组件的数据,避免重复触发 + const lastSentModelsRef = useRef(catalogModels); + // 父 → 子:仅当 prop 数据真的变化(预设切换 / 编辑加载)时才重建 rowId; // 同 shape 时保留现有 rowId,避免编辑过程中焦点丢失。 useEffect(() => { @@ -130,17 +133,22 @@ export function CodexFormFields({ if (catalogRowsMatchModels(current, catalogModels)) return current; return catalogModels.map((m) => createCatalogRow(m)); }); + // 同步更新 ref,避免父组件传入新数据时子→父 effect 误判为本地修改 + lastSentModelsRef.current = catalogModels; }, [catalogModels]); // 子 → 父:rowId 是视图层概念,不应进入持久化数据;剥离后再回传。 + // 注意:依赖数组不包含 catalogModels,避免父→子更新触发子→父回调形成循环。 useEffect(() => { if (!onCatalogModelsChange) return; - if (catalogRowsMatchModels(catalogRows, catalogModels)) return; const next: CodexCatalogModel[] = catalogRows.map( ({ rowId: _rowId, ...rest }) => rest, ); + // 只有当数据真的变化时才通知父组件 + if (catalogRowsMatchModels(catalogRows, lastSentModelsRef.current)) return; + lastSentModelsRef.current = next; onCatalogModelsChange(next); - }, [catalogRows, catalogModels, onCatalogModelsChange]); + }, [catalogRows, onCatalogModelsChange]); const handleLocalRoutingChange = useCallback( (checked: boolean) => {