当我的映射输入组件已经具有唯一键时,为什么会收到“唯一键道具”警告?

时间:2018-08-22 21:09:37

标签: reactjs

我正在尝试在我的应用程序中创建一个表单。我创建了一个input.js组件,该组件导入了我的contact.js组件,然后进行了映射。我得到一个“警告:数组或迭代器中的每个子代都应具有唯一的“键”属性。请在控制台中检查input的呈现方法,但我不明白为什么,因为每个输入组件已设置了唯一键。当我在chrome inspector的react选项卡中检查它们时,它们都有唯一的密钥集。

这是我的contact.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;

这是我的input.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;

我在这里想念什么?

1 个答案:

答案 0 :(得分:3)

即使formElementsArray.map似乎是最有可能的候选者,但在这种情况下,它并不是警告的来源。就像您在评论中提到的那样,每个键在构造上都是唯一的。错误来自input.js,您在其中分配output = [inputElement, label, errorlabel]然后直接渲染{output}。 React认为这是一个数组,但不知道它的大小是固定的,因此期望数组中的每个元素都具有唯一的key属性。如果您在keyinputElementlabel上放置errorLabel道具,警告应该消失。