React组件意外修改props.data,需保留初始值对比控制提交按钮
React表单修改输入时意外修改props.data的问题解决及初始值对比禁用提交按钮实现
问题说明
实现了一个包含n个输入框的React Form组件,通过父组件传入data数组作为输入框初始值。当前遇到两个核心问题:
- 修改输入框值时,父组件传入的
props.data被意外修改; - 无法正确实现「表单值与初始值完全相同时禁用SubmitButton」的功能。
问题根源
- 引用类型共享:数组是引用类型,直接将
props.data赋值给state或在handleChange中直接修改数组,会导致原props.data的引用被篡改; - 非法setState触发无限重渲染:组件函数体中直接判断并调用
setInputs(props.data!),每次渲染都会触发state更新,形成循环; - 初始值未持久保存:没有单独保留初始值的副本,无法准确对比当前值与初始值的差异。
解决方案及修改代码
核心修改点
- 拷贝数组切断引用:所有数组操作都创建新副本,避免修改原引用;
- 用useRef持久保存初始值:ref不会触发重渲染,能始终保留初始值用于对比;
- 用useEffect替代函数体中的setState:仅在
props.data变化时同步state,避免无限循环; - 实现初始值对比逻辑:通过监听inputs变化,结合「字段完整性」和「与初始值是否一致」两个条件控制按钮禁用状态;
- 将OneInput改为受控组件:用
value绑定state值,确保输入框与state同步。
修改后的Form组件
import { useState, useEffect, useRef } from "react"; interface Props { itemsNum: number; types?: string[]; labels?: string[]; submitText?: string; disableOnClick?: boolean; enableOnInput?: boolean; trigger?: number; data?: any[]; onSubmit(inputs: any[]): any; fetchData?(): any[] | Promise<any[]>; } // 数组全等判断函数(若已有可替换) function equalsArr(arr1: any[], arr2: any[]): boolean { if (arr1.length !== arr2.length) return false; for (let i = 0; i < arr1.length; i++) { if (arr1[i] !== arr2[i]) return false; } return true; } export function Form(props: Props) { // 初始化时拷贝props.data,切断引用关联 const [inputs, setInputs] = useState<any[]>(props.data ? [...props.data] : []); const [disabledInputs, setDisabledInputs] = useState<boolean>(false); const [disabledSubmit, setDisabledSubmit] = useState<boolean>(true); // 用ref持久保存初始值 const initialDataRef = useRef<any[]>(props.data ? [...props.data] : []); // 监听props.data变化,同步state并更新初始值 useEffect(() => { if (props.data) { const newData = [...props.data]; setInputs(newData); initialDataRef.current = newData; } }, [props.data]); // 监听inputs变化,自动更新提交按钮禁用状态 useEffect(() => { const hasUndefined = hasUndefinedFields(inputs); const isSameAsInitial = equalsArr(inputs, initialDataRef.current); // 禁用条件:存在未定义字段 或 与初始值完全一致 setDisabledSubmit(hasUndefined || isSameAsInitial); }, [inputs]); function handleChange(e: any, i: number) { const { value } = e.target; // 创建新数组副本,避免修改原引用 const newArr = [...inputs]; newArr[i] = value; setInputs(newArr); } function hasUndefinedFields(arr: any[]): boolean { for (const item of arr) { if (!item) return true; } return false; } function validateInputs(): boolean { for (const input of inputs) { if (!input || String(input).match(/^0+$/)) { // 替换为你的Regex.ZEROS逻辑 return false; } } return true; } function validateArrLen(): boolean { return props.itemsNum >= 0; } function getInputs() { let inps = []; for (let i = 0; i < props.itemsNum; i++) { inps.push( <OneInput key={i} label={props.labels?.length && props.labels[i] ? props.labels[i] : ''} type={(props.types?.length && props.types[i]) ? props.types[i] : 'text'} name={`input${i + 1}`} onChange={(e: any) => handleChange(e, i)} value={inputs[i] ?? ''} // 改为受控组件,绑定value disabled={disabledInputs} /> ); } return inps; } const content = validateArrLen() ? ( <> {getInputs()} <SubmitButton text={props.submitText ? props.submitText : '保存'} // 替换为你的Strings.SAVE常量 disabled={disabledSubmit} onSubmit={(e: any) => { e.preventDefault(); if (validateInputs()) { props.onSubmit(inputs); setDisabledInputs(props.disableOnClick ?? true); setDisabledSubmit(props.disableOnClick ?? true); } else { alert('存在无效字段'); // 替换为你的Errors.NULL_FILEDS_ERROR常量 } }} /> </> ) : ( <div>错误:属性长度冲突</div> ); return content; }
修改后的OneInput组件
interface OneInputProps { label?: string; name: string; type?: string; onChange: any; value?: string | number | undefined; disabled?: boolean; } export function OneInput(props: OneInputProps) { return ( <label> {props.label} <input type={props.type || 'text'} name={props.name} onChange={props.onChange} value={props.value ?? ''} // 绑定value,实现受控组件 disabled={props.disabled} /> </label> ); }
内容的提问来源于stack exchange,提问作者M.Olaby
相关产品推荐
相关产品推荐

