React 组件正在修改自己的 props,而不直接更改它们

问题描述 投票:0回答:1

我创建了一个 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>
reactjs react-hooks react-props
1个回答
0
投票

问题

代码正在改变

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);
}

要点:

  1. 使用功能状态更新来访问当前
    inputs
    状态值
  2. 使用
    Array.map
    创建新的数组引用
  3. 使用映射索引用新值更新正确的数组元素

补充说明

虽然不是您所问的道具突变问题的贡献者,但我觉得我还必须提到您的表单元素的一个奇怪之处,它似乎是“半控制的”。完全 受控 输入使用 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;
}
© www.soinside.com 2019 - 2024. All rights reserved.