ReactJS: Handling form validations - reactjs

I have created the following UI:
Initially I want Movie Name, Ratings and Durations column to have a black border while if a user enters an invalid input (or empty), it should be red. As you can see, Ratings is still red despite the fact that the page has just refreshed. It should be black like its siblings. Can anyone point out why is this happening? Perhaps there is a different way to handle validations in React because of state hooks?
Initial State:
const [validations, setValidations] = useState({
isNameValid: true,
isRatingValid: true,
isDurationValid: true,
});
Submit form if it's valid:
const handleFormSubmit = (event) => {
event.preventDefault();
if (validateInput(newMovie)) {
props.onSubmit(newMovie);
setNewMovie(emptyObject);
}
};
CSS Rule(s):
.invalid {
border-color: rgb(238, 90, 90);
}
validateInput function:
const validateInput = (movieObject) => {
if (movieObject.name.length === 0) {
setValidations((prevState) => {
return { ...prevState, isNameValid: false };
});
} else {
setValidations((prevState) => {
return { ...prevState, isNameValid: true };
});
}
if (movieObject.ratings.length === 0) {
setValidations((prevState) => {
return { ...prevState, isRatingValid: false };
});
} else if (
parseInt(movieObject.ratings) >= 0 &&
parseInt(movieObject.ratings) <= 100
) {
setValidations((prevState) => {
return { ...prevState, isRatingValid: true };
});
}
if (movieObject.duration.length === 0) {
setValidations((prevState) => {
return { ...prevState, isDurationValid: false };
});
} else {
setValidations((prevState) => {
return { ...prevState, isDurationValid: true };
});
}
console.log(validations);
return (
validations.isNameValid &&
validations.isRatingValid &&
validations.isDurationValid
);
};
Based on validations state, className is applied (or not):
<form onSubmit={handleFormSubmit}>
<div className="layout-column mb-15">
<label htmlFor="name" className="mb-3">
Movie Name
</label>
<input
className={`${!validations.isNameValid ? "invalid" : ""}`}
type="text"
id="name"
placeholder="Enter Movie Name"
data-testid="nameInput"
value={newMovie.name}
onChange={handleChangeEvent}
/>
</div>
<div className="layout-column mb-15">
<label htmlFor="ratings" className="mb-3">
Ratings
</label>
<input
className={!validations.isRatingsValid ? "invalid" : ""}
type="number"
id="ratings"
placeholder="Enter Rating on a scale of 1 to 100"
data-testid="ratingsInput"
value={newMovie.ratings}
onChange={handleChangeEvent}
/>
</div>
<div className="layout-column mb-30">
<label htmlFor="duration" className="mb-3">
Duration
</label>
<input
className={!validations.isDurationValid ? "invalid" : ""}
type="text"
id="duration"
placeholder="Enter duration in hours or minutes"
data-testid="durationInput"
value={newMovie.duration}
onChange={handleChangeEvent}
/>
</div>
<div className="layout-row justify-content-end">
<button type="submit" className="mx-0" data-testid="addButton">
Add Movie
</button>
</div>
</form>
Anybody can point out a better way to apply validations? I feel like there is a lot of repetition here and the fact that Ratings has a red border even at the start, how can this be fixed?

Related

Better way to check input values of a form in React/Next Js

I've written a simple form for my web application. I plan on writing a feature that checks to make sure all fields are non-empty and valid, and displaying an error message as a component if not. This is the skeleton:
import { useState } from 'react';
import emailjs from "emailjs-com";
import apiKeys from "../public/credentials/apikeys";
import ContactStyles from "../public/styles/ContactStyles";
function ContactForm() {
const [fieldDict, setFieldDict] = useState(
{
name: "",
email: "",
subject: "",
message: ""
});
function sendEmail(e) {
e.preventDefault();
// TODO: Show success or error message
var blankField = false;
if (fieldDict.name.length === 0) {
console.log("No name"); // To be implemented here and on subsequent lines
blankField = true;
}
if (fieldDict.email.length === 0) {
console.log("No email");
blankField = true;
}
if (fieldDict.subject.length === 0) {
console.log("No subject");
blankField = true;
}
if (fieldDict.message.length === 0) {
console.log("No message");
blankField = true;
}
if (blankField) { return }
emailjs.sendForm(apiKeys.serviceID, apiKeys.templateID, e.target, apiKeys.userID)
.then((result) => {
console.log(result, fieldDict);
}, (error) => {
console.log(error, fieldDict);
});
e.target.reset();
}
return (
<div className="contact-section">
<div className="contact-container">
<h5 className="form-header">Send me an email!</h5>
<form className="contact-form" onSubmit={sendEmail}>
<div className="form-group">
<label className="label">Name</label>
<input className="input" type="text" name="name" autoComplete="off"
onInput={e => {
setFieldDict(prevFieldDict => ({...prevFieldDict, name: e.target.value}));
}}/>
</div>
<div className="form-group">
<label className="label">Email</label>
<input className="input" type="email" name="email" autoComplete="off"
onInput={e => {
setFieldDict(prevFieldDict => ({...prevFieldDict, email: e.target.value}));
}}/>
</div>
<div className="form-group">
<label className="label">Subject</label>
<input className="input" type="subject" name="subject" autoComplete="off"
onInput={e => {
//? Viability of this method?
setFieldDict(prevFieldDict => ({...prevFieldDict, subject: e.target.value}));
}}/>
</div>
<div className="form-group">
<label className="label">Message</label>
<textarea className="input textarea" name="message" rows="6"
onInput={e => {
setFieldDict(prevFieldDict => ({...prevFieldDict, message: e.target.value}));
}}/>
</div>
<div className="submit">
<button type="submit" value="Send">Submit</button>
</div>
</form>
</div>
<style jsx global>{ContactStyles}</style>
</div>
);
}
export default ContactForm;
With every keystroke, the corresponding key (name, email, subject, message) gets updated.
Question
Is there a better way to do this? A more efficient way to do this seems to be only updating the fieldDict dictionary when the use hits submit, but based on how react renders components does this really matter?
Note: The code as is works just fine, I'm just asking if there is a better way to do this? If you want a better way of seeing this, change the content of onInput{...} to e => console.log(e.target.value).
Any insight is much appreciated!
You can iterate through your dict and filter all names that are falsable.
function sendEmail(e) {
e.preventDefault();
const blankFields = Object.keys(fieldDict).filter(fieldName => !fieldDict[fieldName])
blankFields.forEach(fieldName => console.log(`no ${fieldName}`);
if (blankFields.length) { return }
emailjs.sendForm(apiKeys.serviceID, apiKeys.templateID, e.target, apiKeys.userID)
.then((result) => {
console.log(result, fieldDict);
}, (error) => {
console.log(error, fieldDict);
});
e.target.reset();
}

Checkbox validation using React JS

I am currently working on a form having checkboxes which has to be validated using react JS. I need it to show an error saying 'Please select atleast 2 checkbox' if less than 2 checkboxes are checked. I've tried using the if condition but its not working. I have referred a lot of of websites but couldn't come up with a proper solution. Please do help me.
MY CODE:
class App extends React.Component {
state = {
checkbox: "",
checkboxValid: false,
errorMsg: {},
};
validateForm = () => {
const { checkboxValid } = this.state;
this.setState({
formValid: checkboxValid,
});
};
updateCheckbox = (checkbox) => {
this.setState({ checkbox }, this.validateCheckbox);
};
validateCheckbox = () => {
const { checkbox } = this.state;
let checkboxValid = true;
let errorMsg = { ...this.state.errorMsg };
if (checkbox.checked < 2) {
checkboxValid = false;
errorMsg.checkbox = "Please select atleast 2 checkbox";
}
this.setState({ checkboxValid, errorMsg }, this.validateForm);
};
render() {
return (
<div>
<label htmlFor="checkbox">checkbox</label>
<ValidationMessage
valid={this.state.checkboxValid}
message={this.state.errorMsg.checkbox}
/>
<input
type="checkbox"
onChange={(e) => this.updateCheckbox(e.target.value)}
/>
Sports
<br></br>
<input
type="checkbox"
onChange={(e) => this.updateCheckbox(e.target.value)}
/>
Business
<br></br>
<input
type="checkbox"
onChange={(e) => this.updateCheckbox(e.target.value)}
/>
Health
<br></br>
<input
type="checkbox"
onChange={(e) => this.updateCheckbox(e.target.value)}
/>
Society
<br></br>
<div>
<button
className="button"
type="submit"
disabled={!this.state.formValid}
>
Submit
</button>
</div>
</div>
);
}
}
Define count in the state and update it based on the checkbox selection,
state = {
checkbox: "",
checkboxValid: false,
errorMsg: {},
selectedCheckBox: 0
};
Update Logic:-
updateCheckbox = ({ name, checked }) => {
this.setState(
(prev) => ({
checkbox: checked,
selectedCheckBox: checked
? prev.selectedCheckBox + 1
: prev.selectedCheckBox - 1
}),
this.validateCheckbox
);
};
Use the selectedCheckBox count in the state for validation
Completed Code:-
import React from "react";
import "./styles.css";
export default class App extends React.Component {
state = {
checkbox: "",
checkboxValid: false,
errorMsg: {},
selectedCheckBox: 0
};
validateForm = () => {
const { checkboxValid } = this.state;
this.setState({
formValid: checkboxValid
});
};
updateCheckbox = ({ name, checked }) => {
this.setState(
(prev) => ({
checkbox: checked,
selectedCheckBox: checked
? prev.selectedCheckBox + 1
: prev.selectedCheckBox - 1
}),
this.validateCheckbox
);
};
validateCheckbox = () => {
const { checkbox } = this.state;
let checkboxValid = true;
let errorMsg = { ...this.state.errorMsg };
if (this.state.selectedCheckBox < 2) {
checkboxValid = false;
errorMsg.checkbox = "Please select atleast 2 checkbox";
}
this.setState({ checkboxValid, errorMsg }, this.validateForm);
};
render() {
return (
<div>
<label htmlFor="checkbox">checkbox</label>
{/* <ValidationMessage
valid={this.state.checkboxValid}
message={this.state.errorMsg.checkbox}
/> */}
<input
type="checkbox"
name="business"
onChange={(e) => this.updateCheckbox(e.target)}
/>
Sports
<br></br>
<input
type="checkbox"
name="health"
onChange={(e) => this.updateCheckbox(e.target)}
/>
Business
<br></br>
<input
type="checkbox"
name="society"
onChange={(e) => this.updateCheckbox(e.target)}
/>
Health
<br></br>
<input
type="checkbox"
onChange={(e) => this.updateCheckbox(e.target)}
/>
Society
<br></br>
<div>
<button
className="button"
type="submit"
disabled={!this.state.formValid}
>
Submit
</button>
<br />
<b style={{ fontSize: "30px" }}>{this.state.selectedCheckBox}</b>
</div>
</div>
);
}
}
Working Demo - https://codesandbox.io/s/frosty-colden-8hdm4?file=/src/App.js:0-2160
One way to solve this is by having a different state for each checkbox. Set a name for each checkbox so that it can be access by e.target.name
Notice that the name of the input is the same as the state.
state = {
checkbox1: false,
checkbox2: false,
checkboxValid: false,
};
updateCheckbox = (e) => {
this.setState({ e.target.name: e.target.checked });
};
if(this.state.checkbox1 && this.state.checkbox2) {
//both are checked!
}
change input to
<input
name="checkbox1"
type="checkbox"
onChange={this.updateCheckbox}
checked={this.state.checkbox1}
/>

Submit works with second try

I am validating some details, the problem is when the validation is complete and I want to move to the next page with submit, the first time it's receiving the data, and the second time it's checking if the data is true and moves me to the next page.
I have tried to put all the validation process to the OnChange function, but it messes up all of the validation process, I have also tried to put the error variables to the state but I receive an error message that It's constant variables and can't be changed.
import { Link } from 'react-router-dom';
class Profile extends Component {
state = {
details: {
firstName: '',
lastName: '',
id: '',
email: ''
},
error: false,
complete: false
};
OnSubmit = e => {
e.preventDefault();
let re = /^(([^<>()\[\]\\.,;:\s#"]+(\.[^<>()\[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
const { email } = this.state.details;
const { firstName } = this.state.details;
const { lastName } = this.state.details;
const { id } = this.state.details;
let errorid = false;
let errorfirstlast = false;
let erroremail = false;
if (id.length <= 9 && id !== '') {
console.log('trueid');
errorid = false;
} else {
errorid = true;
console.log('falseid');
}
if (re.test(email)) {
console.log('trueemail');
erroremail = false;
} else {
erroremail = true;
console.log('falseemail');
}
if (
firstName !== '' &&
lastName !== '' &&
firstName.substr(0, 1) === firstName.substr(0, 1).toUpperCase() &&
lastName.substr(0, 1) === lastName.substr(0, 1).toUpperCase() &&
!firstName.match(/\d/) &&
!lastName.match(/\d/)
) {
console.log('truefirstlast');
errorfirstlast = false;
} else {
errorfirstlast = true;
console.log('falsefirstlast');
}
if (erroremail === true || errorfirstlast === true || errorid === true) {
this.setState({ error: true });
} else {
this.setState({ error: false });
this.setState({ complete: true });
}
};
OnChange = e => {
e.preventDefault();
this.setState({
details: { ...this.state.details, [e.target.name]: e.target.value }
});
};
render() {
return (
<div>
<div className="container text-center mt-4" style={{ width: '500px' }}>
<form className="px-4 py-3" onSubmit={this.OnSubmit}>
<div className="form-group">
{this.state.error === true ? (
<p className="text-danger">
Some of the details are wrong check the fields above
</p>
) : null}
<label>First Name:</label>
<input
type="text"
className="form-control"
onChange={this.OnChange}
name="firstName"
/>
</div>
<div className="form-group">
<label>Last Name:</label>
<input
type="text"
className="form-control"
onChange={this.OnChange}
name="lastName"
/>
</div>
<div className="form-group">
<label>ID Number:</label>
<input
type="text"
className="form-control"
onChange={this.OnChange}
name="id"
/>
</div>
<div className="form-group">
<label>Email:</label>
<input
type="text"
className="form-control"
onChange={this.OnChange}
name="email"
/>
</div>
{this.state.complete === true ? (
<Link to="/success">
<button type="submit" className="btn btn-secondary mt-3">
Check
</button>
</Link>
) : (
<button type="submit" className="btn btn-secondary mt-3">
Check
</button>
)}
</form>
</div>
</div>
);
}
}
export default Profile;
The problem is that I enter the next page with the second click on the submit button, I want to enter with the first try
The reason is that this.setState(..) is an async function Reference here.
Moreover you're calling multiple state-operations behind each other instead of putting it in one call like:
if (erroremail === true || errorfirstlast === true || errorid === true) {
this.setState({ error: true });
} else {
this.setState({
error: false,
complete: true
});
}
If you want to to an action after this state operation is done you can add a callback to the operation:
if (erroremail === true || errorfirstlast === true || errorid === true) {
this.setState({ error: true });
} else {
this.setState({
error: false,
complete: true
}, this.submitMyStuff);
}
const submitMyStuff = () => {
// submit all my stuff
}

how to check if string contains number with react

I am building a user validation website, I want each input to verify if the string that was entered:
Have uppercase first letter
doesn't contain numbers
doesn't contain "$%^&*()"
I did the first task, but I can't do the last ones.
I have tried !isNaN(firstName) === true and it wont work
import React, { Component } from 'react';
class Profile extends Component {
state = {
details: {
firstName: '',
lastName: '',
ID: '',
Email: ''
},
error: false,
complete: false
};
OnSubmit = e => {
e.preventDefault();
const { firstName } = this.state.details;
if (
firstName.charAt(0) !== firstName.charAt(0).toUpperCase() &&
!isNaN(firstName) === true
) {
this.setState({ error: true });
} else {
this.setState({ complete: true });
}
};
OnChange = e => {
e.preventDefault();
this.setState({
details: { ...this.state.details, [e.target.name]: e.target.value }
});
};
render() {
return (
<div>
<div className="container text-center mt-4" style={{ width: '500px' }}>
<form className="px-4 py-3" onSubmit={this.OnSubmit}>
<div className="form-group">
{this.state.error === true ? (
<p className="text-danger">
Some of the details are wrong check the fields above
</p>
) : null}
<label>First Name:</label>
<input
type="text"
className="form-control"
onChange={this.OnChange}
name="firstName"
/>
</div>
<div className="form-group">
<label>Last Name:</label>
<input
type="text"
className="form-control"
onChange={this.OnChange}
name="lastName"
/>
</div>
<div className="form-group">
<label>ID Number:</label>
<input
type="text"
className="form-control"
onChange={this.OnChange}
name="ID"
/>
</div>
<div className="form-group">
<label>Email:</label>
<input
type="text"
className="form-control"
onChange={this.OnChange}
name="Email"
/>
</div>
<button type="submit" className="btn btn-secondary mt-3">
Check
</button>
</form>
</div>
</div>
);
}
}
export default Profile;
function validateName(name) {
var isValidName = true;
if(/[!##$%^&*(),.?":{}|<>]/g.test(name) || !/^[A-Z]/.test(name) || /\d+/g.test(name)) {
isValidName = false;
}
return isValidName;
}
validateName("David")
You can use regex.
!firstName.match(/\d/)
\d checks for the numbers
First split firstName, then check for Number in that array
OnSubmit = e => {
e.preventDefault();
const { firstName } = this.state.details;
let firstNameArr = firstName.split('');
for(value of firstName.split('')){
if (!isNaN(value) {
this.setState({ error: true });
} else {
this.setState({ complete: true });
}
}
};
This is how I would do it:
const test1 = "%2mfas1k";
const test2 = '123';
const test3 = 'test';
function test(str) {
const match = str.match(/\d+/g);
const isArray = Array.isArray(match);
if(isArray) return match.map(Number);
return false
}
// If test return a result not falsy, contains a number
console.log(test(test1)); // [2, 1]
console.log(test(test2)); // [123]
console.log(test(test3)); // false
Here is a working example, i have split each part into its own checker to make it easier to understand.
let string = "Asdfsdf$32";
let special_characters = ['$','%','^','&','*','(',')'];
let string_array = string.split('');
// Upper case check
if(string[0] === string[0].toUpperCase()) {
console.log("First letter is uppercase")
} else {
console.log("First letter is not uppercase")
}
// No numbers check
if(string.match(/\d/)) {
console.log("Digit Found")
} else {
console.log("No Digit Found")
}
// Special Characters
if(string_array.find(item => special_characters.includes(item))) {
console.log("Special Character Found")
} else {
console.log("No Special Character Found")
}
export function checkDigit(username){
if(username.match(/\d/)) {
console.log("Digit Found")
return true;
}else {
return false;
}
}
let username = this.state.name;
if(checkDigit(username)){
showInfoAlert(NAME_VALIDATION_DIGIT)
return
}

Grabbing React form field values for submission

Given a React form, I'm having trouble getting the value from the selected radio button, and text box if other is selected. I should be able to pass the fields into the send() for the post, but not sure how to grab them.
class CancelSurvey extends React.Component {
constructor (props) {
super(props)
this.state = {
reasons: [],
reason: {}
}
this.processData = this.processData.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
this.otherSelected = this.state.reason === "otheroption";
}
componentDidMount () {
this.fetchContent(this.processData)
}
/**
* Fetch reasons
*/
fetchContent (cb) {
superagent
.get('/api/user/survey')
.then(cb)
}
/**
* Set state after reasons have been fetched
* #param data
*/
processData (data) {
this.setState({
reasons: data.body
})
}
handleSubmit (e) {
e.preventDefault()
let reason = this.state.reason
if (reason === 'otheroption') {
reason = this.state.otherreason
}
console.log(reason)
superagent
.post('/api/user/survey')
.send({
optionId: this.state.reason.reason_id,
optionText: this.state.reason.client_reason,
otherReasonText: this.state.otherreason
})
.then(function (res) {
console.log('Survey Sent!')
})
}
/**
* render
*/
render (props) {
const content = this.props.config.contentStrings
const reason = this.state.reasons.map((reason, i) => {
return (
<div className='fieldset__item' key={i}>
<label>{reason.client_reason}</label>
<input type='radio'
id={reason.reason_id}
value={reason.client_reason}
name='reason'
checked={this.state.reason.reason_id === reason.reason_id}
onChange={() => this.setState({reason})} />
</div>
)
})
return (
<div className='survey'>
<h2 className='heading md'>{content.memberCancel.exitSurvey.heading}</h2>
<p className='subpara'>{content.memberCancel.exitSurvey.subHeading}</p>
<form id='exit-survey' onSubmit={this.handleSubmit}>
<fieldset className='fieldset'>
{ reason }
<label>Other reason not included above:</label>
<input type='radio'
id='otheroption'
name='reason'
value={this.state.reason.otherreason}
onChange={() => this.setState({reason:{reason_id: 70, client_reason: 'other'}})} />
<input className='valid'
type='text'
id='otheroption'
name='othertext'
placeholder={content.memberCancel.exitSurvey.reasonPlaceholder}
onChange={(event) => this.setState({otherreason: event.target.value})} />
</fieldset>
<div className='footer-links'>
<button className='btn btn--primary btn--lg' onClick={this.handleSubmit}>{content.memberCancel.exitSurvey.button}</button>
</div>
</form>
</div>
)
}
}
export default CancelSurvey
Your variables aren't correct. I've update them to what I think is correct.
handleSubmit (e) {
e.preventDefault()
superagent
.post('/api/user/survey')
.send({
optionId: this.state.reason.reason_id,
optionText: this.state.reason.client_reason,
otherReasonText: this.state.reason.otherreason
})
.then(function (res) {
console.log('Survey Sent!')
})
.catch(function (err) {
console.log('Survey submission went wrong...')
})
}
/**
* render
*/
render (props) {
const content = this.props.config.contentStrings
const reason = this.state.reasons.map((reason, i) => {
return (
<div className='fieldset__item' key={i}>
<label>{reason.client_reason}</label>
<input
type='radio'
id={reason.reason_id}
name='reason'
checked={this.state.reason.reason_id === reason.reason_id}
value={reason.client_reason}
onChange={() => this.setState({reason})} />
</div>
)
})
return (
<div className='survey'>
<h2 className='heading md'>{content.memberCancel.exitSurvey.heading}</h2>
<p className='subpara'>{content.memberCancel.exitSurvey.subHeading}</p>
<form id='exit-survey' onSubmit={this.handleSubmit}>
<fieldset className='fieldset'>
{ reason }
<label>Other reason not included above:</label>
<input type='radio'
id='otheroption'
name='otheroption'
value={this.state.reason.otherreason}
checked={this.state.reason.reason_id === 0}
onChange={() => this.setState({ reason: {reason_id: 0, client_reason: ""} })} />
<input className='valid'
type='text'
id='othertext'
name='othertext'
value={this.state.reason.otherreason}
placeholder={content.memberCancel.exitSurvey.reasonPlaceholder}
onChange={(event) => this.setState({ reason: {reason_id: 0, client_reason: "", otherreason: event.target.value} })} />
</fieldset>
<div className='footer-links'>
<button className='btn btn--primary btn--lg' onClick={this.handleSubmit}>{content.memberCancel.exitSurvey.button}</button>
</div>
</form>
</div>
);
}

Resources