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;
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
@Kokovin Vladislav is right. To put this in code, you can do this in all your input value
s:
<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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With