Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Uncontrolled input of type text to be controlled warning

I'm trying to create a multi step registration form using React and Redux.

The main component is as follows :

import React, {PropTypes} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import * as actionCreators from '../../actions/actionCreators';
import countries from '../../data/countries';

import RegistrationFormStepOne from './registrationFormStepOne';
import RegistrationFormStepTwo from './registrationFormStepTwo';
import RegistrationFormStepThree from './registrationFormStepThree';
import RegistrationFormStepFour from './registrationFormStepFour';

class RegistrationPage extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            user: Object.assign({}, this.props.userData),
            fileNames: {},
            selectedFile: {},
            icons: {
                idCard: 'upload',
                statuten: 'upload',
                blankLetterhead: 'upload',
                companyPhoto: 'upload'
            },
            step: 1,
            errors: {}
        };

        this.setUser = this.setUser.bind(this);
        this.onButtonClick = this.onButtonClick.bind(this);
        this.onButtonPreviousClick = this.onButtonPreviousClick.bind(this);
        this.changeCheckboxState = this.changeCheckboxState.bind(this);
        this.onFileChange = this.onFileChange.bind(this);
        this.routerWillLeave = this.routerWillLeave.bind(this);
    }

    componentDidMount() {
        this.context.router.setRouteLeaveHook(this.props.route, this.routerWillLeave);
    }

    routerWillLeave(nextLocation) {
        if (this.state.step > 1) {
            this.setState({step: this.state.step - 1});
            return false;
        }
    }

    getCountries(){
        return countries;
    }


    setUser(event) {
        const field = event.target.name;
        const value = event.target.value;

        let user = this.state.user;
        user[field] = value;
        this.setState({user: user});

    }

    validation(){
        const user = this.state.user;
        const languageReg = this.props.currentLanguage.default.registrationPage;
        let formIsValid = true;
        let errors = {};

        if(!user.companyName){
            formIsValid = false;
            errors.companyName = languageReg.companyNameEmpty;
        }

        if(!user.btwNumber){
            formIsValid = false;
            errors.btwNumber = languageReg.btwNumberEmpty;
        }

        if(!user.address){
            formIsValid = false;
            errors.address = languageReg.addressEmpty;
        }

        if(!user.country){
            formIsValid = false;
            errors.country = languageReg.countryEmpty;
        }

        if(!user.zipcode){
            formIsValid = false;
            errors.zipcode = languageReg.zipcodeEmpty;
        }

        if(!user.place){
            formIsValid = false;
            errors.place = languageReg.placeEmpty;
        }


        if(!user.firstName){
            formIsValid = false;
            errors.firstName = languageReg.firstnameEmpty;
        }



        this.setState({errors: errors});
        return formIsValid;
    }

    onFileChange(name, event) {
        event.preventDefault();
        let file = event.target.value;

        let filename = file.split('\\').pop(); //We get only the name of the file
        let filenameWithoutExtension = filename.replace(/\.[^/.]+$/, ""); //We get the name of the file without extension

        let user = this.state.user;
        let fileNames = this.state.fileNames;
        let selectedFile = this.state.selectedFile;
        let icons = this.state.icons;

        switch (name.btnName) {
            case "idCard" :
                fileNames[name.btnName] = filenameWithoutExtension;
                //Check if file is selected
                if(file){
                    selectedFile[name.btnName] = "fileSelected";
                    user["idCardFile"] = true;
                    icons["idCard"] = "check";
                }else{
                    selectedFile[name.btnName] = "";
                    user["idCardFile"] = false;
                    icons["idCard"] = "upload";
                }
                break;
            case "statuten" :
                fileNames[name.btnName] = filenameWithoutExtension;

                //Check if file is selected
                if(file){
                    selectedFile[name.btnName] = "fileSelected";
                    user["statutenFile"] = true;
                    icons["statuten"] = "check";
                }else{
                    selectedFile[name.btnName] = "";
                    user["statutenFile"] = false;
                    icons["statuten"] = "upload";
                }
                break;
            case "blankLetterhead" :
                fileNames[name.btnName] = filenameWithoutExtension;

                //Check if file is selected
                if(file){
                    selectedFile[name.btnName] = "fileSelected";
                    user["blankLetterheadFile"] = true;
                    icons["blankLetterhead"] = "check";
                }else{
                    selectedFile[name.btnName] = "";
                    user["blankLetterheadFile"] = false;
                    icons["blankLetterhead"] = "upload";
                }
                break;
            default:
                fileNames[name.btnName] = filenameWithoutExtension;
                //Check if file is selected
                if(file){
                    selectedFile[name.btnName] = "fileSelected";
                    user["companyPhotoFile"] = true;
                    icons["companyPhoto"] = "check";
                }else{
                    selectedFile[name.btnName] = "";
                    user["companyPhotoFile"] = false;
                    icons["companyPhoto"] = "upload";
                }
        }

        this.setState({user: user, fileNames: fileNames, selectedFile: selectedFile, icons: icons});
    }

    changeCheckboxState(event) {
        let chcName = event.target.name;
        let user = this.state.user;

        switch (chcName) {
            case "chcEmailNotificationsYes":
                user["emailNotifications"] = event.target.checked;
                break;
            case "chcEmailNotificationsNo":
                user["emailNotifications"] = !event.target.checked;
                break;
            case "chcTerms":
                if(typeof this.state.user.terms === "undefined"){
                    user["terms"] = false;
                }else{
                    user["terms"] = !this.state.user.terms;
                }

                break;
            case "chcSmsYes":
                user["smsNotifications"] = event.target.checked;
                break;
            default:
                user["smsNotifications"] = !event.target.checked;
        }
        this.setState({user: user});
        this.props.actions.userRegistration(this.state.user);
    }

    onButtonClick(name, event) {
        event.preventDefault();
        this.props.actions.userRegistration(this.state.user);
        switch (name) {
            case "stepFourConfirmation":
                this.setState({step: 1});
                break;
            case "stepTwoNext":
                this.setState({step: 3});
                break;
            case "stepThreeFinish":
                this.setState({step: 4});
                break;
            default:
                if(this.validation()) {
                   this.setState({step: 2});
                }
        }
    }


    onButtonPreviousClick(){
        this.setState({step: this.state.step - 1});
    }

    render() {
        const languageReg = this.props.currentLanguage.default.registrationPage;

        console.log(this.state.user);
        let formStep = '';
        let step = this.state.step;
        switch (step) {
            case 1:
                formStep = (<RegistrationFormStepOne user={this.props.userData}
                                                     onChange={this.setUser}
                                                     onButtonClick={this.onButtonClick}
                                                     countries={this.getCountries(countries)}
                                                     errors={this.state.errors}
                                                     step={step}/>);
                break;
            case 2:
                formStep = (<RegistrationFormStepTwo user={this.props.userData}
                                                     onChange={this.setUser}
                                                     onButtonClick={this.onButtonClick}
                                                     onButtonPreviousClick={this.onButtonPreviousClick}
                                                     errors={this.state.errors}/>);
                break;
            case 3:
                formStep = (<RegistrationFormStepThree user={this.props.userData}
                                                       onFileChange={this.onFileChange}
                                                       onButtonClick={this.onButtonClick}
                                                       onButtonPreviousClick={this.onButtonPreviousClick}
                                                       errors={this.state.errors}
                                                       fileNames={this.state.fileNames}
                                                       icons={this.state.icons}
                                                       fileChosen={this.state.selectedFile}/>);
                break;

            default:
                formStep = (<RegistrationFormStepFour user={this.props.userData}
                                                      onChange={this.setUser}
                                                      onChangeCheckboxState={this.changeCheckboxState}
                                                      onButtonClick={this.onButtonClick}
                                                      onButtonPreviousClick={this.onButtonPreviousClick}
                                                      errors={this.state.errors}/>);
        }

        return (
            <div className="sidebar-menu-container" id="sidebar-menu-container">

                <div className="sidebar-menu-push">

                    <div className="sidebar-menu-overlay"></div>

                    <div className="sidebar-menu-inner">
                        <div className="contact-form">
                            <div className="container">
                                <div className="row">
                                    <div className="col-md-10 col-md-offset-1 col-md-offset-right-1">
                                        {React.cloneElement(formStep, {currentLanguage: languageReg})}
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        );
    }
}

RegistrationPage.contextTypes = {
    router: PropTypes.object
};

function mapStateToProps(state, ownProps) {
    return {
        userData: state.userRegistrationReducer
    };
}

function mapDispatchToProps(dispatch) {
    return {
        actions: bindActionCreators(actionCreators, dispatch)
    };
}

export default connect(mapStateToProps, mapDispatchToProps)(RegistrationPage);

The first step component is as follows

    import React from 'react';
import Button from '../../common/formElements/button';
import RegistrationFormHeader from './registrationFormHeader';
import TextInput from '../../common/formElements/textInput';
import SelectInput from '../../common/formElements/selectInput';

const RegistrationFormStepOne = ({user, onChange, onButtonClick, errors, currentLanguage, countries}) => {

  const language = currentLanguage;

  return (
    <div className="contact_form">
      <form role="form" action="" method="post" id="contact_form">
        <div className="row">
          <RegistrationFormHeader activeTab={0} currentLanguage={language}/>
          <div className="hideOnBigScreens descBox">
            <div className="headerTitle">{language.businessInfoConfig}</div>
            <div className="titleDesc">{language.businessBoxDesc}</div>
          </div>
          <div className="col-lg-12">
            <h6 className="registrationFormDesc col-lg-10 col-lg-offset-1 col-lg-offset-right-2 col-xs-12">
              {language.businessDesc}
            </h6>
            <div className="clearfix"></div>
            <div className="col-sm-6">
              <TextInput
                type="text"
                name="companyName"
                label={language.companyNameLabel}
                labelClass="control-label"
                placeholder={language.companyNameLabel}
                className="templateInput"
                id="company"
                onChange={onChange}
                value={user.companyName}
                errors={errors.companyName}
              />
            </div>
            <div className="col-sm-6">
              <TextInput
                type="text"
                name="btwNumber"
                label={language.vatNumberLabel}
                placeholder={language.vatNumberLabel}
                className="templateInput"
                id="btwNumber"
                onChange={onChange}
                value={user.btwNumber}
                errors={errors.btwNumber}
              />
            </div>

            <div className="col-sm-12" style={{marginBottom: 25}}>
              <TextInput
                type="text"
                name="address"
                label={language.addressLabel}
                placeholder={language.address1Placeholder}
                className="templateInput"
                id="address"
                onChange={onChange}
                value={user.address}
                errors={errors.address}
              />
            </div>

            <div className="col-sm-12" style={{marginBottom: 25}}>
              <TextInput
                type="text"
                name="address1"
                placeholder={language.address2Placeholder}
                className="templateInput"
                id="address"
                onChange={onChange}
                value={user.address1}
                errors=""
              />
            </div>

            <div className="col-sm-12">
              <TextInput
                type="text"
                name="address2"
                placeholder={language.address3Placeholder}
                className="templateInput"
                id="address"
                onChange={onChange}
                value={user.address2}
                errors=""
              />
            </div>

              <div className="col-sm-3">
              <SelectInput name="country"
                           label={language.selectCountryLabel}
                           onChange={onChange}
                           options={countries}
                           className="templateInput selectField"
                           defaultOption={language.selectCountry}
                           value={user.country}
                           errors={errors.country}
                           />
                </div>

            <div className="col-sm-3">

              <TextInput
                type="text"
                name="zipcode"
                label={language.zipcodeLabel}
                placeholder={language.zipcodeLabel}
                className="templateInput"
                id="zipcode"
                onChange={onChange}
                value={user.zipcode}
                errors={errors.zipcode}
              />
            </div>
            <div className="col-sm-6">
              <TextInput
                type="text"
                name="place"
                label={language.placeLabel}
                placeholder={language.placeLabel}
                className="templateInput"
                id="place"
                onChange={onChange}
                value={user.place}
                errors={errors.place}
              />
            </div>
          </div>
          <div className="clearfix"></div>
          <div className="col-lg-12" style={{marginLeft: 15, marginTop: 30}}>
            <Button onClick={onButtonClick.bind(this)}
                    name="stepOneNext"
                    value={language.btnNext}
                    icon="arrow-circle-right"
                    style={{margin: '0 auto 60px'}}/>
          </div>
        </div>
      </form>
    </div>
  );
};

export default RegistrationFormStepOne;

I try to add some simple validation and I've added validation function in my main component and then I check on button click if the returned value true or false is. If it's true, than I set step state to a appropriate value. And it works if I validate only the form fields of the first step, but when I try to also validate one or more form fields of the next step (now I'm trying to validate also the first field of the second step)

if(!user.firstName){
        formIsValid = false;
        errors.firstName = languageReg.firstnameEmpty;
    }

I get than

Warning: TextInput is changing an uncontrolled input of type text to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component.

Without the validation function works everything perfect.

Any advice?

EDIT

    import React, {propTypes} from 'react';
import _ from 'lodash';

const TextInput = ({errors, style, name, labelClass, label, className, placeholder, id, value, onChange, type}) => {
  let wrapperClass = "form-group";

  if (errors) {
    wrapperClass += " " + "inputHasError";
  }

  return (
    <div className={wrapperClass} style={style}>
      <label htmlFor={name} className={labelClass}>{label}</label>
      <input type={type}
             className={className}
             placeholder={placeholder}
             name={name}
             id={id}
             value={value}
             style={{}}
             onChange={onChange}
      />
      <div className="errorBox">{errors}</div>
    </div>
  );
};

TextInput.propTypes = {
  name: React.PropTypes.string.isRequired,
  label: React.PropTypes.string,
  onChange: React.PropTypes.func.isRequired,
  type: React.PropTypes.string.isRequired,
  id: React.PropTypes.string,
  style: React.PropTypes.object,
  placeholder: React.PropTypes.string,
  className: React.PropTypes.string,
  labelClass: React.PropTypes.string,
  value: React.PropTypes.string,
  errors: React.PropTypes.string
};

export default TextInput;

This is second step component :

import React from 'react';
import Button from '../../common/formElements/button';
import RegistrationFormHeader from './registrationFormHeader';
import TextInput from '../../common/formElements/textInput';


const RegistrationFormStepTwo = ({user, onChange, onButtonClick, onButtonPreviousClick, errors, currentLanguage}) => {
    const language = currentLanguage;

    return (
        <div className="contact_form">
            <form role="form" action="" method="post" id="contact_form">
                <div className="row">
                    <RegistrationFormHeader activeTab={1} currentLanguage={language}/>
                    <div className="hideOnBigScreens descBox">
                        <div className="headerTitle">{language.personalInfoConfig}</div>
                        <div className="titleDesc">{language.personalBoxDesc}</div>
                    </div>
                    <div className="col-lg-12">
                        <h6 className="registrationFormDesc col-lg-10 col-lg-offset-1 col-lg-offset-right-2 col-xs-12">
                            {language.personalDesc}
                        </h6>
                        <div className="col-lg-6 col-md-6 col-sm-6 col-xs-12">
                            <TextInput
                                type="text"
                                name="firstName"
                                label={language.firsnameLabel}
                                placeholder={language.firsnameLabel}
                                className="templateInput"
                                id="name"
                                onChange={onChange}
                                value={user.firstName}
                                errors={errors.firstName}
                            />
                        </div>
                        <div className="col-lg-6 col-md-6 col-sm-6 col-xs-12">
                            <TextInput
                                type="text"
                                name="lastName"
                                label={language.lastnameLabel}
                                placeholder={language.lastnameLabel}
                                className="templateInput"
                                id="name"
                                onChange={onChange}
                                value={user.lastName}
                                errors={errors.lastName}
                            />
                        </div>

                        <div className="col-lg-6 col-md-6 col-sm-6 col-xs-12">
                            <TextInput
                                type="text"
                                name="phone"
                                label={language.phoneLabel}
                                placeholder={language.phoneLabel}
                                className="templateInput"
                                id="phone"
                                onChange={onChange}
                                value={user.phone}
                                errors={errors.phone}

                            />
                        </div>

                        <div className="col-lg-6 col-md-6 col-sm-6 col-xs-12">
                            <TextInput
                                type="text"
                                name="mobilePhone"
                                label={language.mobileLabel}
                                placeholder={language.mobileLabel}
                                className="templateInput"
                                id="phone"
                                style={{}}
                                onChange={onChange}
                                value={user.mobilePhone}
                                errors={errors.mobilePhone}
                            />
                        </div>
                        <div className="clearfix"></div>

                        <div className="col-lg-12 col-md-12 col-sm-12 col-xs-12">
                            <TextInput
                                type="text"
                                name="email"
                                id="email"
                                label={language.emailLabel}
                                placeholder={language.emailLabel}
                                className="templateInput"
                                style={{}}
                                onChange={onChange}
                                value={user.email}
                                errors={errors.email}
                            />
                        </div>

                        <div className="col-lg-12 col-md-12 col-sm-12 col-xs-12">
                            <TextInput
                                type="text"
                                name="userName"
                                label={language.usernameLabel}
                                placeholder={language.usernameLabel}
                                className="templateInput"
                                id="name"
                                onChange={onChange}
                                value={user.userName}
                                errors={errors.userName}
                            />
                        </div>

                        <div className="col-lg-6 col-md-6 col-sm-6 col-xs-12">
                            <TextInput
                                type="password"
                                name="password"
                                label={language.passwordLabel}
                                placeholder={language.passwordLabel}
                                className="templateInput"
                                id="password"
                                onChange={onChange}
                                value={user.password}
                                errors={errors.password}
                            />
                        </div>
                        <div className="col-lg-6 col-md-6 col-sm-6 col-xs-12">
                            <TextInput
                                type="password"
                                name="confirmPassword"
                                label={language.passwordConfirmLabel}
                                placeholder={language.passwordConfirmLabel}
                                className="templateInput"
                                id="password"
                                onChange={onChange}
                                value={user.confirmPassword}
                                errors={errors.confirmPassword}
                            />
                        </div>

                    </div>
                    <div className="clearfix"></div>
                    <div className="col-lg-6 col-xs-6" style={{marginTop: 30}}>
                        <Button onClick={onButtonPreviousClick}
                                name="btnPrevious"
                                value={language.btnPrevious}
                                icon="arrow-circle-left"
                                style={{marginRight: 10, float: 'right'}}/>
                    </div>
                    <div className="col-lg-6 col-xs-6" style={{marginTop: 30}}>
                        <Button onClick={onButtonClick} name="stepTwoNext" value={language.btnNext}
                                icon="arrow-circle-right" style={{marginLeft: 10, float: 'left'}}/>
                    </div>
                </div>
            </form>
        </div>

    );
};

export default RegistrationFormStepTwo;
like image 550
Boky Avatar asked Jun 24 '16 13:06

Boky


2 Answers

This is why the warning exists: When the value is specified as undefined, React has no way of knowing if you intended to render a component with an empty value or if you intended for the component to be uncontrolled. It is a source of bugs.

You could do a null/undefined check, before passing the value to the input.

a source

like image 71
Kokovin Vladislav Avatar answered Sep 18 '22 01:09

Kokovin Vladislav


@Kokovin Vladislav is right. To put this in code, you can do this in all your input values:

<TextInput
    // your other code
    value={user.firstName || ''}
/>

That is, if you don't find the value of first name, then give it an empty value.

like image 27
Kaka Ruto Avatar answered Sep 20 '22 01:09

Kaka Ruto