Reputation: 275
I created the form for multiple inputs, where the specific input data shall be validated at the time of data entry and once again for all data just before the submission of the form to the backend.
The conditions to submit: all fields are mandatory and the data is valid.
My program works, but I don't like that I'm repeating the validation code in 2 places: in ErrorOutput and hadleSubmit.
In ErrorOutput I check the data and, if necessary, display an error message.
In handleSubmit I just check the data without displaying of error message and if all data is valid, I confirm submitting.
How can I improve my example to prevent the repetition of this code, but the data validation was also at the time of data entry and before submission?
import React from 'react'
import { render } from 'react-dom'
const ErrorOutput = props => {
let name = props.name
let inputValue = props.case
let submit = props.submit
// Data validation
if (name === 'firstName') {
if (!inputValue.match(/^[a-zA-Z]+$/) && inputValue.length > 0) {
return <span>Letters only</span>
} else if (submit && inputValue.length === 0) {
return <span>Required</span>
}
return <span></span>
}
if (name === 'telNo') {
if(!inputValue.match(/^[0-9]+$/) && inputValue.length > 0) {
return <span>Numbers only</span>
} else if (submit && inputValue.length === 0) {
return <span>Required</span>
}
return <span></span>
}
}
class App extends React.Component {
constructor(props){
super(props)
this.state = {
firstName: '',
telNo: '',
submit: false
}
}
handleSubmit(e){
e.preventDefault()
let submit = true
let error = true
const { firstName, telNo } = this.state
this.setState ({submit: submit})
// Repeat the data validation before submission
if (firstName === '' || !firstName.match(/^[a-zA-Z]+$/)) {
error = true
} else if (telNo === '' || !telNo.match(/^[0-9]+$/)) {
error = true
} else {
error = false
}
// Submited if all data is valid
if (!error) {
// send data
return alert('Success!')
}
}
handleValidation(e) {
this.setState({
[e.target.name]: e.target.value
})
}
render() {
return (
<form onSubmit={this.handleSubmit.bind(this)}>
<div>
<label>
First name:
</label>
<input
type='text'
name ='firstName'
value = {this.state.firstName}
onChange = {this.handleValidation.bind(this)}
/>
<ErrorOutput case={this.state.firstName} name={'firstName'} submit = {this.state.submit} />
</div>
<div>
<label>
Phone number:
</label>
<input
type='tel'
name ='telNo'
value = {this.state.telNo}
onChange = {this.handleValidation.bind(this)}
/>
<ErrorOutput case={this.state.telNo} name={'telNo'} submit = {this.state.submit} />
</div>
<button>
Submit
</button>
</form>
)
}
}
render(
<App />,
document.getElementById('root')
)
Upvotes: 2
Views: 747
Reputation: 3259
You could extract a FormItem
component:
class FormItem extends React.Component {
render() {
return (
<div>
<label>
{this.props.label}
</label>
<input
{...this.props.input}
/>
<ErrorOutput
case={this.props.input.value}
name={this.props.input.name}
submit={this.props.onSubmit}
/>
</div>
);
}
}
and use it in your App
:
render() {
return (
<form onSubmit={this.handleSubmit.bind(this)}>
<FormItem label='First name:' input={{
type: 'text'
name: 'firstName'
value: this.state.firstName,
onChange: this.handleValidation.bind(this)
}}
onSubmit={this.state.submit}
/>
<FormItem label='Phone number:' input={{
type:'tel'
name :'telNo'
value : {this.state.telNo}
onChange : {this.handleValidation.bind(this)}
}}
onSubmit={this.state.submit}
/>
<button>
Submit
</button>
</form>
)
}
this is where libraries like react-final-form and redux-form become handy.
ErrorOutput
component should not validate anything, it is not a responsibility of a component. Instead, you could validate your values on inputs blur event and before submit:
class App extends React.Component {
constructor(props){
super(props)
this.state = {
firstName: '',
telNo: '',
submit: false,
errors: {},
invalid: false,
}
}
handleSubmit(e){
e.preventDefault()
if (this.validate()) {
// handle error
} else {
// submit
}
}
validate = () => {
const { firstName, telNo } = this.state
const errors = {}
let invalid = false;
if (firstName === '' || !firstName.match(/^[a-zA-Z]+$/)) {
errors.firstName = 'first name is required'
invalid = true;
} else if (telNo === '' || !telNo.match(/^[0-9]+$/)) {
telNo.telNo = 'telNo is required'
invalid = true;
}
this.setState({
invalid,
errors,
})
return invalid;
}
render() {
return (
<form onSubmit={this.handleSubmit.bind(this)}>
<FormItem label='First name:' input={{
type: 'text',
name: 'firstName',
value: this.state.firstName,
onChange: e => this.setState({ firstName: e.target.value }),
onBlur: () => this.validate(),
}}
/>
<FormItem label='Phone number:' input={{
type: 'tel',
name: 'telNo',
value: this.state.telNo,
onChange: e => this.setState({ telNo: e.target.value }),
onBlur: () => this.validate(),
}}
/>
<button>
Submit
</button>
</form>
)
}
}
and FormItem and ErrorOutput:
const ErrorOutput = ({ error }) => <span>{error}</span>
class FormItem extends React.Component {
render() {
return (
<div>
<label>
{this.props.label}
</label>
<input
{...this.props.input}
/>
{this.props.error && <ErrorOutput error={this.props.error} />}
</div>
);
}
}
Upvotes: 2
Reputation: 140
const ErrorOutput = ({ errorText }) => <span>{errorText}</span>;
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
firstName: "",
telNo: "",
submit: false,
errors: {} //Add errors object to the state.
};
}
handleSubmit(e) {
e.preventDefault();
const errors = this.validateData();
if (Object.keys(errors).length === 0) {
alert("Success");
}
//else errors exist
this.setState({ errors });
}
validateData = () => {
let errors = {};
const { firstName, telNo } = this.state; // read the values to validate
if (firstName.length === 0) {
errors.firstName = "Required";
} else if (firstName.length > 0 && !firstName.match(/^[a-zA-Z]+$/)) {
errors.firstName = "Letters only";
}
if (telNo.length === 0) {
errors.telNo = "Required";
} else if (telNo.length > 0 && !telNo.match(/^[0-9]+$/)) {
errors.telNo = "Numbers only";
}
return errors;
};
handleValidation(e) {
this.setState({
[e.target.name]: e.target.value
});
}
render() {
const { errors } = this.state; // read errors from the state
return (
<form onSubmit={this.handleSubmit.bind(this)}>
<div>
<label>First name:</label>
<input
type="text"
name="firstName"
value={this.state.firstName}
onChange={this.handleValidation.bind(this)}
/>
{errors.firstName && <ErrorOutput errorText={errors.firstName} />}
</div>
<div>
<label>Phone number:</label>
<input
type="tel"
name="telNo"
value={this.state.telNo}
onChange={this.handleValidation.bind(this)}
/>
{errors.telNo && <ErrorOutput errorText={errors.telNo} />}
</div>
<button>Submit</button>
</form>
);
}
}
render(<App />, document.getElementById("root"));
Upvotes: 2