You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Next.js表单页面无限循环问题求助

Next.js表单无限循环问题修复方案

问题根源

  1. 父子组件状态不同步:子组件ProductDetails未同步父组件传递的productDetailsData到自身state,导致状态更新时触发父组件重新渲染,进而引发子组件重复执行逻辑。
  2. 无限制的状态更新:子组件的校验状态isValid每次变化都会通知父组件,父组件更新后又触发子组件重新校验,形成闭环循环。
  3. 表单默认值传递错误: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 23:00:53