我正在尝试使用React和Redux创建一个多步骤注册表单。
主要组成部分如下:
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);
第一步组件如下
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;
我尝试添加一些简单的验证,并且我已在主要组件中添加了验证功能,然后如果返回值为true或false,则检查按钮单击。如果它是真的,那么我将步骤状态设置为适当的值。如果我只验证第一步的表单字段,但是当我尝试同时验证下一步的一个或多个表单字段时(现在我也尝试验证第二步的第一个字段),它就可以工作
if(!user.firstName){
formIsValid = false;
errors.firstName = languageReg.firstnameEmpty;
}
我得到了
警告:TextInput正在更改要控制的文本类型的不受控制的输入。输入元素不应从不受控制切换到受控制(或反之亦然)。决定在组件的生命周期内使用受控或不受控制的输入元素。
没有验证功能,一切都很完美。
有什么建议吗?
修改
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;
这是第二步:
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;
答案 0 :(得分:16)
这就是警告存在的原因:当将值指定为undefined时,React无法知道您是否打算使用空值呈现组件,或者是否打算使组件不受控制。它是错误的来源。
在将值传递给输入之前,您可以执行null / undefined检查。
答案 1 :(得分:1)
@Kokovin Vladislav是对的。要将其放入代码中,您可以在所有输入value
中执行此操作:
<TextInput
// your other code
value={user.firstName || ''}
/>
也就是说,如果找不到名字的值,则给它一个空值。