Next.js表单页面无限循环问题求助
Next.js表单无限循环问题修复方案
问题根源
- 父子组件状态不同步:子组件
ProductDetails未同步父组件传递的productDetailsData到自身state,导致状态更新时触发父组件重新渲染,进而引发子组件重复执行逻辑。 - 无限制的状态更新:子组件的校验状态
isValid每次变化都会通知父组件,父组件更新后又触发子组件重新校验,形成闭环循环。 - 表单默认值传递错误:
CommonForm使用子组件内部的formData作为默认值,而非父组件的props,导致表单重复初始化并触发状态更新。
修复步骤
1. 同步父组件props到子组件state
在ProductDetails组件中添加监听,确保父组件状态变化时子组件同步更新:
export const ProductDetails: React.FC<ProductDetailsProps> = ({ onChange, productDetailsData, onValidationChange, isEdit }) => { const { t } = useTranslation(); const [formData, setFormData] = useState(productDetailsData); const [isValid, setIsValid] = useState(false); // 新增:同步父组件props到子组件state useEffect(() => { setFormData(productDetailsData); }, [productDetailsData]); // ... 其余原有代码 };
2. 限制状态更新触发条件
在子组件和父组件中,仅当状态值真正变化时才执行更新,避免重复触发:
子组件ProductDetails修改:
const isValidRef = useRef(isValid); useEffect(() => { isValidRef.current = isValid; }, [isValid]); useEffect(() => { // 仅当isValid值变化时通知父组件 if (isValid !== isValidRef.current) { isValidRef.current = isValid; onValidationChange(isValid); } }, [isValid, onValidationChange]); const handleSubmit = (data: ProductDetailsData, isFormValid?: boolean) => { setFormData(data); const newIsValid = isFormValid ?? false; // 仅当校验结果变化时更新状态 if (newIsValid !== isValid) { setIsValid(newIsValid); } };
父组件ProductForm修改:
const isValidRef = useRef(isValid); useEffect(() => { isValidRef.current = isValid; }, [isValid]); const handleValidationChange = (newIsValid: boolean) => { // 仅当新值与当前值不同时更新 if (newIsValid !== isValidRef.current) { setIsValid(newIsValid); } };
3. 修正表单默认值传递
将CommonForm的defaultFormData改为父组件传递的props,避免子组件内部状态引发的重复初始化:
return ( <CommonForm schema={PRODUCT_DETAILS_SCHEMA} fields={fields} defaultFormData={productDetailsData} // 替换为父组件props formButtons={[]} onSubmit={handleSubmit} formAlignment={FormAlignment.HORIZONTAL_THREE_QUARTERS} /> );
最终修复后关键代码片段
ProductDetails组件
'use client'; import { useEffect, useState, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import TextInput from '../common/form-element/textInput'; import DropDown from '../common/form-element/dropDown'; import RadioBoxes from '../common/form-element/radioBoxes'; import CommonForm from '../common/commonForm'; import { PRODUCT_DETAILS_SCHEMA, ProductDetailsData } from '@/model/product.type'; import { FormAlignment, FormField, LabelType, LayoutType } from '@/model/form.type'; import { BOOLEAN_YES_OR_NO_OPTIONS, ENTITY, PRODUCT_TYPE } from '@/model/common.type'; type ProductDetailsProps = { onChange: (productDetailsData: ProductDetailsData) => void; productDetailsData: ProductDetailsData; onValidationChange: (isValid: boolean) => void; isEdit: boolean; }; export const ProductDetails: React.FC<ProductDetailsProps> = ({ onChange, productDetailsData, onValidationChange, isEdit }) => { const { t } = useTranslation(); const [formData, setFormData] = useState(productDetailsData); const [isValid, setIsValid] = useState(false); const isValidRef = useRef(isValid); useEffect(() => { setFormData(productDetailsData); }, [productDetailsData]); useEffect(() => { onChange(formData); }, [formData, onChange]); useEffect(() => { if (isValid !== isValidRef.current) { isValidRef.current = isValid; onValidationChange(isValid); } }, [isValid, onValidationChange]); const fields: FormField<any>[] = [ { name: 'ProductCode', label: 'Product.code', component: TextInput, layout: LayoutType.HALF, isDisabled: isEdit, isRequired: true, labelType: LabelType.LABEL }, { name: 'Entity', label: 'Product.entity', component: DropDown, layout: LayoutType.HALF, options: ENTITY, isDisabled: isEdit, isRequired: true, labelType: LabelType.LABEL }, { name: 'Version', label: 'Product.version', component: TextInput, layout: LayoutType.HALF, isDisabled: false, isRequired: true, labelType: LabelType.LABEL }, { name: 'PlanType', label: 'Product.planType', component: DropDown, layout: LayoutType.HALF, options: PRODUCT_TYPE, isDisabled: false, isRequired: false, labelType: LabelType.LABEL }, { name: 'ProductNameENG', label: 'Product.nameENG', component: TextInput, layout: LayoutType.FULL, isDisabled: false, isRequired: true, labelType: LabelType.LABEL }, { name: 'ProductNameBMY', label: 'Product.nameBMY', component: TextInput, layout: LayoutType.FULL, isDisabled: false, isRequired: true, labelType: LabelType.LABEL }, { name: 'AdvantageProgram', label: 'Product.advantageProgram', component: RadioBoxes, layout: LayoutType.HALF, options: BOOLEAN_YES_OR_NO_OPTIONS, isDisabled: false, isRequired: true, labelType: LabelType.LABEL }, { name: 'IsJuvenile', label: 'Product.isJuvenile', component: RadioBoxes, layout: LayoutType.HALF, options: BOOLEAN_YES_OR_NO_OPTIONS, isDisabled: false, isRequired: true, labelType: LabelType.LABEL } ]; const handleSubmit = (data: ProductDetailsData, isFormValid?: boolean) => { setFormData(data); const newIsValid = isFormValid ?? false; if (newIsValid !== isValid) { setIsValid(newIsValid); } }; return ( <CommonForm schema={PRODUCT_DETAILS_SCHEMA} fields={fields} defaultFormData={productDetailsData} formButtons={[]} onSubmit={handleSubmit} formAlignment={FormAlignment.HORIZONTAL_THREE_QUARTERS} /> ); };
ProductForm组件(handleValidationChange部分)
const ProductForm: React.FC<ProductFormProps> = ({ productCode, onBack }) => { const { t } = useTranslation(); const [productDetailsData, setProductDetailsData] = useState<ProductDetailsData>(PORDUCT_DETAILS_DATA); const [productBenefitData, setProductBenefitData] = useState<ProductBenefitData>(PORDUCT_BENEFIT_DATA); const [productQuestionData, setProductQuestionData] = useState<ProductQuestionData>(PORDUCT_QUESTION_DATA); const [isValid, setIsValid] = useState(false); const isValidRef = useRef(isValid); useEffect(() => { isValidRef.current = isValid; }, [isValid]); const handleValidationChange = (newIsValid: boolean) => { if (newIsValid !== isValidRef.current) { setIsValid(newIsValid); } }; // ... 其余原有代码 };
内容的提问来源于stack exchange,提问作者nfs
相关产品推荐
相关产品推荐

