为什么当我映射的输入组件已经具有唯一键时,我会收到"unique key prop"警告?



我正在尝试在我的反应应用程序中创建一个表单。我创建了一个输入.js组件,该组件导入到我的联系人.js组件中,然后进行映射。我收到一个"警告:数组或迭代器中的每个孩子都应该有一个唯一的"键"道具。在控制台内检查input" 的渲染方法,但我不明白为什么,因为每个输入组件都已经设置了一个唯一的键。当我在 chrome 检查器的 react 选项卡中检查它们时,它们都有一个唯一的键集。

这是我的联系人.js组件:

import React, { Component } from 'react';
import Input from './Input/input';
import Button from './Button/Button';
import Spinner from '../UI/Spinner/Spinner';
import {checkValidity} from '../../shared/utility';
import axios from '../../axios-contact';
class ContactForm extends Component {
state = {
contactForm: {
name: {
elementType: 'input',
elementConfig: {
inputprops: {
type: 'text',
id: 'name',
name: 'name',                                       
required: 'required'  
} ,
label: 'Name',                
htmlFor: 'name',
invalid: 'Please enter your firstname',
value: '',              
},                
validation: {
required: true,
minLength: 2,
maxLength: 30
},
valid: false,
touched: false
},
company: {
elementType: 'input',
elementConfig: {
inputprops: {
type: 'text',
id: 'company',
name: 'company',                 
required: 'required'                    
},                
label: 'Company',
htmlFor: 'company', 
invalid: 'Please enter your company name',
value: '',
},
validation: {
required: true,
minLength: 2,
maxLength: 30
},
valid: false,
touched: false
},
location: {
elementType: 'input',
elementConfig: {
inputprops: {
type: 'text',
id: 'location',
name: 'location',                 
required: 'required'
},
label: 'Company location (city / country)',
htmlFor: 'location',
invalid: 'Please enter your location',
value: '',
},
validation: {
required: true,
minLength: 2,
maxLength: 30
},
valid: false,
touched: false
},
email: {
elementType: 'email',
elementConfig: {
inputprops: {
type: 'email',
id: 'email',
name: 'email',                 
required: 'required'                    
},
label: 'Email',
htmlFor: 'email',
invalid: 'Please enter a propper email address',
value: '',
},
validation: {
required: true,
isEmail: true,
minLength: 7,
maxLength: 40
},
valid: false,
touched: false
},
phone: {
elementType: 'input',
elementConfig: {
inputprops: {
type: 'tel',
id: 'phone',
name: 'phone',                 
required: false                    
},
label: 'Phone',
htmlFor: 'phone',
invalid: 'Please enter a propper phone number',
value: '',
},
validation: {
required: false,
minLength: 6,
maxLength: 30
},
valid: true,
touched: false
},
message: {
elementType: 'textarea',
elementConfig: {
inputprops: {
type: 'textarea',
id: 'message',
name: 'message',                  
required: 'required', 
rows: 4                   
},
label: 'Message',
htmlFor: 'message',
invalid: 'Please enter a message',
value: '',
},
validation: {
required: true,
minLength: 2,
maxLength: 500
},
valid: false,
touched: false
},
compliance: {
elementType: 'checkbox',
containerClass: 'custom-control custom-checkbox',
inputClass: 'custom-control-input',
elementConfig: {
inputprops: {
type: 'checkbox',
id: 'gdpr',
name: 'gdpr',                    
required: 'required'                    
},
label: 'I consent to having this website store my submitted information so they can respond to my inquiry.',
htmlFor: 'gdpr',  
invalid: 'Please give your consent before proceeding',                 
value: '',
},
validation: {
required: true,
isCheckbox: true,
isToggled: false
},
valid: false,
touched: false           
}
},
formIsValid: false,
loading: false,
sent: false
}
contactHandler = ( event ) => {
event.preventDefault();
this.setState( { loading: true } );
const formData = {}
for (let formElementIdentifier in this.state.contactForm) {
formData[formElementIdentifier] = this.state.contactForm[formElementIdentifier].elementConfig.value;
}
axios.post('/contacts.json', formData)
.then(response => {
this.setState({ loading: false, sent: true });
console.log(formData);
})
.catch(error => {
this.setState({ loading: false, sent: true });
console.log(formData);
});
}    
inputChangedHandler = (event, inputIdentifier) => {
const updatedContactForm = {
...this.state.contactForm
};
const updatedFormElement = {
...updatedContactForm[inputIdentifier]
};
updatedFormElement.elementConfig.value = event.target.value;
updatedFormElement.valid = checkValidity(updatedFormElement.elementConfig.value, updatedFormElement.validation);
updatedFormElement.touched = true;
updatedFormElement.validation.isToggled = !updatedFormElement.validation.isToggled;
updatedContactForm[inputIdentifier] = updatedFormElement;
let formIsValid = true;
for ( let inputIdentifier in updatedContactForm) {
formIsValid = updatedContactForm[inputIdentifier].valid && formIsValid;
}
this.setState({contactForm: updatedContactForm, formIsValid: formIsValid});
}
render () {
const formElementsArray = [];
for (let key in this.state.contactForm) {
formElementsArray.push({
id: key,
config: this.state.contactForm[key]
});
}
let form = (
<form onSubmit={this.contactHandler} name="contact">
{formElementsArray.map(formElement =>(
<Input 
key={formElement.id}
elementType={formElement.config.elementType}
containerClass={formElement.config.containerClass}
inputClass={formElement.config.inputClass}
elementConfig={formElement.config.elementConfig}
value={formElement.config.value}
invalid={!formElement.config.valid}
shoudValidate={formElement.config.validation}
touched={formElement.config.touched}
checked={formElement.config.validation.isToggled}
changed={(event) => this.inputChangedHandler(event, formElement.id)}
exited={(event) => this.inputChangedHandler(event, formElement.id)} /> 
))}
<Button disabled={!this.state.formIsValid} />
</form>
);
if (this.state.loading) { 
form = <Spinner />
}
if (this.state.sent) { 
form = <p id="contact-message" className="contact-message">Thank you for your message.<br /> We will respond as soon as possible.</p>
}
return (
<div className="contact">
<section id="contact-form" className="contact-form">
<h1>Contact</h1>
{form}      
</section>
</div>
)
}
};
export default ContactForm;

这是我的输入.js组件:

import React from 'react';
import { NavLink } from 'react-router-dom';
const input = ( props ) => {
let label = <label htmlFor={props.elementConfig.htmlFor}>{props.elementConfig.label}</label>;
let inputElement = null;
let errorlabel = null;
let inputClass = ['input'];
const errorid = [props.elementConfig.id];
if(props.invalid && props.shoudValidate && props.touched) {
inputClass.push('error');
}
switch (props.elementType) {
case ('input'):
inputElement = <input 
className ={inputClass.join(' ')}
{...props.elementConfig.inputprops} 
value={props.elementConfig.value}
onChange={props.changed}
onBlur={props.exited} />;            
break;
case ('email'):
inputElement = <input 
className ={inputClass.join(' ')}
{...props.elementConfig.inputprops} 
value={props.elementConfig.value}
onChange={props.changed}
onBlur={props.exited} />;            
break;
case ( 'textarea' ):
inputElement = <textarea 
className ={inputClass.join(' ')}
{...props.elementConfig.inputprops} 
value={props.elementConfig.value}
onChange={props.changed}
onBlur={props.exited} />;
break;
case ( 'checkbox' ):
inputElement = <input 
className ={[props.inputClass, inputClass.join(' ')].join(' ')}
{...props.elementConfig.inputprops} 
value={!props.checked}
onChange={props.changed} />;
label = <label htmlFor={props.elementConfig.htmlFor} className="custom-control-label">This form collects your name, e-mail, phone number, company name, and location so that we may correspond with you. Read our <NavLink to="/privacy" exact>privacy policy</NavLink> for more information. By submitting the form, you consent to have StackApp collect the listed information.</label>;
break;
default:
inputElement = <input 
className ={inputClass.join(' ')}
{...props.elementConfig.inputprops} 
value={props.elementConfig.value}
onChange={props.changed}
onBlur={props.exited} />;
}
if(props.invalid && props.touched) {
errorlabel = <label id={errorid.join('-error')} className="error" htmlFor={props.elementConfig.htmlFor}>{props.elementConfig.invalid}</label>
};
let output = null;
if(props.elementType === 'checkbox') {
output = [inputElement, label, errorlabel];            
} else {
output = [label, inputElement, errorlabel];
}
return (
<div role="group" className={props.containerClass}>
{output}
</div>
)
};
export default input;

我在这里错过了什么?

尽管formElementsArray.map似乎是最有可能的候选者,但在这种情况下,它不是警告的来源。就像您在评论中提到的,您的每个键在构造上都是唯一的。错误来自输入.js您在其中分配output = [inputElement, label, errorlabel]然后直接呈现{output}。React 看到这是一个数组,但不知道它是固定大小的,并希望数组中的每个元素都有一个唯一的keyprop。如果你在inputElementlabelerrorLabel上放一个key道具,警告应该会消失。

最新更新