在 React 中,如何命名属于数组一部分的表单字段?

In React, how do I name a field of my form that is part of an array?

我正在构建 React 16.13.0 应用程序。在我的表单中,我想提交数据(一个地址)作为数组的一部分,所以我这样设置我的状态...

  constructor(props) {
    super(props);

    this.state = {
      countries: [],
      provinces: [],
      errors: [],
      newCoop: {
        name: '',
        types: [],
        addresses: [{
          formatted: '',
          locality: {
            name: '',
            postal_code: '',
            state: ''
          },
          country: FormContainer.DEFAULT_COUNTRY,
        }],
        enabled: true,
        email: '',
        phone: '',
        web_site: '' 
      },

然后我创建了这些函数来管理对输入字段的更改...

  handleInput(e) {
    let self=this
    let value = e.target.value;
    let name = e.target.name;
    this.setValue(self.state.newCoop,name,value)
  }

  setValue = (obj,is, value) => {
       if (typeof is == 'string')
         return this.setValue(obj,is.split('.'), value);
       else if (is.length === 1 && value!==undefined) { 
         return this.setState({obj: obj[is[0]] = value});
       } else if (is.length === 0)
         return obj;
       else
         return this.setValue(obj[is[0]],is.slice(1), value);
  }

...
                <Input inputType={'text'}
                   title= {'Street'} 
                   name= {'addresses[0].formatted'}
                   value={this.state.newCoop.addresses[0].formatted} 
                   placeholder = {'Enter address street'}
                   handleChange = {this.handleInput}
                   errors = {this.state.errors} 
                  /> {/* Address street of the cooperative */}

Input.jsx 文件如下所示...

const Input = (props) => {
    return (  
  <div className="form-group">
      <FormLabel>{props.title}</FormLabel>
      <FormControl
            isInvalid={props.errors && Boolean(props.errors[props.name])}
            type={props.type}
            id={props.name}
            name={props.name}
            value={props.value}
            placeholder={props.placeholder}
            onChange={props.handleChange}
          />

      {props.errors && props.errors[props.name] && (
          <FormControl.Feedback type="invalid">
                 {props.errors[props.name].map((error, index) => (
                     <div key={`field-error-${props.name}-${index}`} className="fieldError">{error}</div>
                 ))} 
          </FormControl.Feedback>
      )}
  </div>
    )
}

export default Input;

但是,当我尝试更改值时,出现以下错误。我不确定我还需要做什么来命名我的组件,以便我可以成功地更改它的值。我宁愿不更改我的构造函数中的数据结构,但我愿意这样做。

TypeError: Cannot set property 'formatted' of undefined
FormContainer.setValue
src/containers/FormContainer.jsx:127
  124 | if (typeof is == 'string')
  125 |   return this.setValue(obj,is.split('.'), value);
  126 | else if (is.length === 1 && value!==undefined) { 
> 127 |   return this.setState({obj: obj[is[0]] = value});
      | ^
  128 | } else if (is.length === 0)
  129 |   return obj;
  130 | else

你的代码很混乱,这是你问题的一部分,你的代码的另一个问题是在反应状态下嵌套对象不是好的做法。您可以了解更多 .

这是您可以使用代码设置状态的示例,但是请注意,这是解决问题的糟糕方法:

handleInput(e) {
  let value = e.target.value;
  this.setState(prevState =>{
  ...prevState,
  newCoop: {
    ...prevState.newCoop
    addresses: [
        {
        ...prevState.newCoop[0].addresses
        formatted: value
        }
      ]
  }
  })
}

问题:

Cannot set property 'formatted' of undefined

// Reason : because you can't access obj["addresses[0]"]["formatted"]

// Solution : it should look something like obj["addresses"][0]["formatted"] 

因为您按 . 拆分字符串,所以您得到的结果是

[
  "addresses[0]",
  "formatted"
]

现在你已经成功拆分了字符串,

您正在尝试通过名称获取对象,特别是 obj["addresses[0]"],但您无法像这样访问对象索引,

它会给你 undefined,所以你会得到上面的错误。您可以通过代码段下面的 运行 检查确切的错误,

const obj = {
    name: '',
    types: [],
    addresses: [{
        formatted: '',
        locality: {
            name: '',
            postal_code: '',
            state: ''
      },
    }],
};

const names = "addresses[0].formatted".split(".")

console.log("obj['addresses[0]'] ===>" , obj[names[0]])

console.log("obj['addresses[0]']['formatted'] ===>" , obj[names[0]][names[1]])


解决方案:

所以现在的问题是,如果不是obj["addresses[0]"],那么解决方案是obj["addresses"]["0"]

所以你有 2 个选项 :

首先:把这个addresses[0].formatted改成addresses.0.formatted

其次: 你需要用 .split(/[\[\].]+/)

分开刺

我更喜欢第二个选项,因为这个 addresses[0].formatted 看起来像真实的表单名称,它应该是这样的,您也可以在下面的代码片段中检查。

const obj = {
    name: '',
    types: [],
    addresses: [{
        formatted: '',
        locality: {
            name: '',
            postal_code: '',
            state: ''
      },
    }],
};

const names = "addresses[0].formatted".split(/[\[\].]+/)

console.log("obj['addresses'] ==>" , obj[names[0]])
console.log("obj['addresses']['0'] ==>" , obj[names[0]][names[1]])
console.log("obj['addresses']['0']['formatted'] ==>" , obj[names[0]][names[1]][names[2]])


注意:

现在,一旦你解决了这个问题,真正的问题就出现了,obj: obj[is[0]] = value,这里 obj 是对象所以这会抛出错误,还有你的 setValue 函数仅限于该功能,它应该是通用的

handleInput = e => {
    let name = e.target.name;
    let value = e.target.value;
    const keys = name.split(/[\[\].]+/);
    this.setState(this.updateValue(this.state, keys, value));
};

// I've created a recursive function such that it will create a 
// copy of nested object so that it won't mutate state directly

// obj : your state
// name : input name
// value : value that you want to update
updateValue = (obj, name, value, index = 0) => {
    if (name.length - 1 > index) {
        const isArray = Array.isArray(obj[name[index]]);
        obj[name[index]] = this.updateValue(
            isArray ? [...obj[name[index]]] : { ...obj[name[index]] },
            name,
            value,
            index + 1
        );
    } else {
        obj = { ...obj, [name[index]]: value };
    }
    return obj;
};

工作演示: