我创建了一个 React 组件,它创建了一种
n
输入形式和自定义的 SubmitButton
。我从父组件传递 data
(一个数组属性)作为每个输入字段中需要显示的初始值。问题是组件变得有点复杂,现在它正在修改 props.data
属性 - 由于某种原因 - 当我在本地状态变量 props.data
中设置属性 inputs
时,程序正在 改变 值每当任何 props.data
字段的值发生变化时,都会改变 input
的值。
我的目标是保持 props.data
不变,正如它应该的那样,然后将其值与当前的 changed inputs
值进行比较,因为我希望此表单组件在用户尝试时将 SubmitButton
设置为禁用提交与初始值具有相同值的表单。
代码来了:
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[]>;
}
export function Form(props: Props) {
const [inputs, setInputs] = useState<any[]>([])
const [disabledInputs, setDisabledInputs] = useState<boolean>(false)
const [disabledSubmit, setDisabledSubmit] = useState<boolean>(true)
if(!equalsArr(props.data!, inputs)) {
setInputs(props.data!)
}
function handleChange(e: any, i: number) {
const { value } = e.target;
const newArr = inputs
newArr[i] = value
setInputs(newArr)
const isUndefined = hasUndefinedFields(inputs)
if (!isUndefined)
setDisabledSubmit(false)
else
setDisabledSubmit(true)
}
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(Regex.ZEROS))
return false
return true;
}
function validateArrLen(): boolean {
if (props.itemsNum < 0)
return false
return true
}
function getInputs() {
let inps = []
for (let i = 0; i < props.itemsNum!; i++) {
inps.push(
<OneInput
key={i}
label={props.labels?.length! > 0 ? props.labels![i]! : ''}
type={(props.types?.length! && props.types![i]!) ? props.types![i]! : 'text'}
name={`input${i + 1}`}
onChange={(e: any) => handleChange(e, i)}
defaultValue={inputs[i]}
disabled={disabledInputs}
/>
)
}
return inps
}
const content = validateArrLen() ?
<>
{getInputs()}
{/* Submit */}
<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>{"Error: Conflict in lengths of properites."}</div>
return content
}
OneInput
组件代码:
import { useState } from "react"
interface OneInputProps {
label?: string
name: string
type?: string
onChange: any
defaultValue?: string | number | undefined;
disabled?: boolean
}
export function OneInput(props: OneInputProps) {
const content = <label>
{label}
<input
type={props.type ? props.type : 'text'}
name={props.name}
onChange={props.onChange}
defaultValue={props.defaultValue}
disabled={props.disabled}
/>
</label>;
return <>{content}</>
}
我正在
parent
组件中执行此操作;
// code..
<table>
....
return <tr key={gs.id}>
<td>{gs.description}</td>
<Form
itemsNum={2}
types={['number', 'number']}
disableOnClick={true}
data={[123, 233]}
onSubmit={(inputs: any) => console.log("Submitted!"))}
/>
</tr>
....
</table>
代码正在改变
inputs
处理程序中的 handleChange
状态
function handleChange(e: any, i: number) {
const { value } = e.target;
const newArr = inputs // <-- newArr is reference to inputs state
newArr[i] = value // <-- mutation!!
setInputs(newArr)
const isUndefined = hasUndefinedFields(inputs)
if (!isUndefined)
setDisabledSubmit(false)
else
setDisabledSubmit(true)
}
更新 React 状态时,正在更新的所有状态和嵌套状态应浅复制到 new 对象引用中,例如在本例中是一个新的 Javascript 数组。
考虑以下重构建议:
function handleChange(e: any, i: number) {
const { value } = e.target;
setInputs(inputs => inputs.map((arr, index) =>
index === i
? value
: err
));
const isUndefined = hasUndefinedFields(inputs);
setDisabledSubmit(isUndefined);
}
要点:
inputs
状态值Array.map
创建新的数组引用虽然不是您所问的道具突变问题的贡献者,但我觉得我还必须提到您的表单元素的一个奇怪之处,它似乎是“半控制的”。完全 受控 输入使用 React 状态以及
onChange
和 value
属性,完全 不受控 输入使用 defaultValue
属性。您的代码使用 defaultValue
属性,但传递 onChange
处理程序并维护本地状态。我的建议是转换为完全受控输入。
一个输入
interface OneInputProps {
label?: string
name: string
type?: string
onChange: any
value: string | number;
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}
disabled={props.disabled}
/>
</label>
);
}
表格
function getInputs() {
const inps = [];
for (let i = 0; i < props.itemsNum!; i++) {
inps.push(
<OneInput
key={i}
label={props.labels?.length! > 0 ? 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] ?? ""}
disabled={disabledInputs}
/>
);
}
return inps;
}