I'm attempting to add validation for radio buttons and check boxes within the StepZilla wizard. Essentially not allowing the user to proceed unless a choice has been selected. The way I have it now the user is allowed to skip the first step without first selecting either yes/no on the first radio question and I can't figure out why. I've added choice logic to the functions before rendering, but can't figure out where the issue is occurring.
I'm fairly certain it has something to do with the choiceVal: (data.choice != null)
line I added within _validatedata. My thinking is that the validation is such that the choice should not be null, but maybe that's incorrect?
'use strict';
import React, { Component } from 'react';
export default class Step1 extends Component {
constructor(props) {
super(props);
this.state = {
email: props.getStore().email,
name: props.getStore().name,
choice: props.getStore().choice
};
this._validateOnDemand = true; // this flag enables onBlur validation as user fills forms
this.validationCheck = this.validationCheck.bind(this);
this.isValidated = this.isValidated.bind(this);
}
componentDidMount() {}
componentWillUnmount() {}
isValidated() {
const userInput = this._grabUserInput(); // grab user entered vals
const validateNewInput = this._validateData(userInput); // run the new input against the validator
let isDataValid = false;
// if full validation passes then save to store and pass as valid
if (Object.keys(validateNewInput).every((k) => { return validateNewInput[k] === true })) {
if (this.props.getStore().email != userInput.email || this.props.getStore().name != userInput.name || this.props.getStore().choice != userInput.choice) { // only update store of something changed
this.props.updateStore({
...userInput,
savedToCloud: false // use this to notify step4 that some changes took place and prompt the user to save again
}); // Update store here (this is just an example, in reality you will do it via redux or flux)
}
isDataValid = true;
}
else {
// if anything fails then update the UI validation state but NOT the UI Data State
this.setState(Object.assign(userInput, validateNewInput, this._validationErrors(validateNewInput)));
}
return isDataValid;
}
validationCheck() {
if (!this._validateOnDemand)
return;
const userInput = this._grabUserInput(); // grab user entered vals
const validateNewInput = this._validateData(userInput); // run the new input against the validator
this.setState(Object.assign(userInput, validateNewInput, this._validationErrors(validateNewInput)));
}
_validateData(data) {
return {
nameVal: /^[a-zA-Z] [a-zA-Z] $/.test(data.name), // required: anything besides N/A
emailVal: /^[a-zA-Z0-9.!#$%&'* /=?^_`{|}~-] @[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test(data.email),
choiceVal: (data.choice != null) // required: regex w3c uses in html5
}
}
_validationErrors(val) {
const errMsgs = {
nameValMsg: val.nameVal ? '' : 'Please enter a full name',
emailValMsg: val.emailVal ? '' : 'A valid email is required',
choiceValMsg: val.choiceVal ? '' : 'A choice is required'
}
return errMsgs;
}
_grabUserInput() {
return {
name: this.refs.name.value,
email: this.refs.email.value,
choice: this.refs.choice.value
};
}
render() {
// explicit class assigning based on validation
let notValidClasses = {};
if (typeof this.state.nameVal == 'undefined' || this.state.nameVal) {
notValidClasses.nameCls = 'no-error col-md-8';
}
else {
notValidClasses.nameCls = 'has-error col-md-8';
notValidClasses.nameValGrpCls = 'val-err-tooltip';
}
if (typeof this.state.emailVal == 'undefined' || this.state.emailVal) {
notValidClasses.emailCls = 'no-error col-md-8';
}
else {
notValidClasses.emailCls = 'has-error col-md-8';
notValidClasses.emailValGrpCls = 'val-err-tooltip';
}
if (typeof this.state.choiceVal == 'undefined' || this.state.choiceVal) {
notValidClasses.choiceCls = 'no-error col-md-8';
}
else {
notValidClasses.choiceCls = 'has-error col-md-8';
notValidClasses.choiceValGrpCls = 'val-err-tooltip';
}
Split the return below so it's easier to view.
return (
<div className="step step1">
<div className="row">
<form id="Form" className="form-horizontal">
<div className="form-group">
<label className="col-md-12 control-label">
<h1>Step 1</h1>
</label>
</div>
<div className="row content">
<div className="col-md-12">
<legend>Are you happy?</legend>
<div className={notValidClasses.choiceCls}>
<input
ref="choice"
type="radio"
id="yes"
name="choice"
value="yes"
onblur={this.validationCheck}
defaultValue={this.state.choice}
required />
<label form="yes">Yes</label>
<input
ref="choice"
type="radio"
id="no"
name="choice"
value="no"
onblur={this.validationCheck}
defaultValue={this.state.choic} />
<label form="no">No</label>
</div>
<div className={notValidClasses.choiceValGrpCls}>{this.state.choiceValMsg}</div>
</div>
</div>
<div className="form-group col-md-12 content form-block-holder">
<label className="control-label col-md-4">
Name
</label>
<div className={notValidClasses.nameCls}>
<input
ref="name"
autoComplete="off"
className="form-control"
required
defaultValue={this.state.name}
onBlur={this.validationCheck} />
<div className={notValidClasses.nameValGrpCls}>{this.state.nameValMsg}</div>
</div>
</div>
<div className="form-group col-md-12 content form-block-holder">
<label className="control-label col-md-4">
Email
</label>
<div className={notValidClasses.emailCls}>
<input
ref="email"
autoComplete="off"
type="email"
placeholder="[email protected]"
className="form-control"
required
defaultValue={this.state.email}
onBlur={this.validationCheck} />
<div className={notValidClasses.emailValGrpCls}>{this.state.emailValMsg}</div>
</div>
</div>
</form>
</div>
</div>
)
}
}
CodePudding user response:
My guess would be the default store value props.getStore().choice
is null. You might need to change the store such that this is defaulted to false or you could default it when you set the initial component state:
this.state = {
email: props.getStore().email,
name: props.getStore().name,
choice: props.getStore().choice ?? false
};
It could well be true of email and name as well but whilst its not causing a problem (because the regex would return false over null) its also bad practice. They should be empty string explicitly to begin with.
CodePudding user response:
Just wanted to update saying that I got the validation to work with these additions, everything in between //**//
is new.
this.state = {
email: props.getStore().email,
name: props.getStore().name,
choice: props.getStore().selectedOption
};
//**// this.onValueChange = this.onValueChange.bind(this); //**//
this._validateOnDemand = true; // this flag enables onBlur validation as user fills forms
this.validationCheck = this.validationCheck.bind(this);
this.isValidated = this.isValidated.bind(this);
}
//**//
onValueChange(event) {
this.setState({
selectedOption: event.target.value
});
}
//**//
[...]
_grabUserInput() {
return {
name: this.refs.name.value,
email: this.refs.email.value,
//**// choice: this.state.selectedOption //**//
};
}
After return
<input
ref="choice"
type="radio"
value="no"
//**// checked={this.state.selectedOption === "no"}
onChange={this.onValueChange} //**//
onBlur={this.validationCheck}
/>