Improving readability of multiple if statements with common pattern - javascript

I am doing some JavaScript front-end and I have a heavy load of forms, all of which need validation. As of now I am using this structure :
function validateForm() {
let form = document.forms["form-add-consumer"];
let id = form["input-id"].value;
let lastName = form["input-last-name"].value;
let firstName = form["input-first-name"].value;
...
let missing = false;
if (lastName.trim() === "") {
document.getElementById("input-last-name-error").className = "error";
missing = true;
}
if (firstName.trim() === "") {
document.getElementById("input-first-name-error").className = "error";
missing = true;
}
if(missing){
return false
} else {
return buildRequest(id, firstName, lastName, ...);
}
}
As you can see, for large forms the function will quickly grow. The code is a bit redundant for each field:
Declare form field
Check its value against a boolean condition
If boolean failed, display the error label and set the failed boolean to true to not send the request
How could I improve this code without complexyfing it too much (no library if possible) ?

Perhaps you could create an object that contians per-field validators, with selectors for respective fields, so that you can perform the nessisary validation in a more concise way like so:
function validateForm() {
let form = document.forms["form-add-consumer"];
let id = form["input-id"].value;
let lastName = form["input-last-name"].value;
let firstName = form["input-first-name"].value;
...
// Construct an object with selectors for the fields as keys, and
// per-field validation functions as values like so
const fieldsToValidate = {
'#input-id' : value => value.trim() !== '',
'#input-last-name' : value => value.trim() !== '',
'#input-first-name' : value => value.trim() !== '',
...,
'#number-field' : value => parseInt(value) > 0, // Different logic for number field
...
}
const invalidFields = Object.entries(fieldsToValidate)
.filter(entry => {
// Extract field selector and validator for this field
const fieldSelector = entry[0];
const fieldValueValidator = entry[1];
const field = form.querySelector(fieldSelector);
if(!fieldValueValidator(field.value)) {
// For invalid field, apply the error class
field.className = 'error'
return true;
}
return false;
});
// If invalid field length is greater than zero, this signifies
// a form state that failed validation
if(invalidFields.length > 0){
return false
} else {
return buildRequest(id, firstName, lastName, ...);
}
}

Related

Check all values in && comparison?

JavaScript is known to only check the first variable in a && comparison in case the first variable returns false. Is there a way to 'ask' JavaScript to check both variables i.e. when they are methods?
For example: Suppose you have 2 methods that validate 2 separate user inputs:
const validateEmail = value => {
if(value.contains('#')){
setShowEmailError(false);
return true;
}
setShowEmailError(true);
return false;
};
const validatePswd = value => {
if(value !== ''){
setShowPswdError(false);
return true;
}
setShowPswdError(true);
return false;
};
Then check both conditions:
if(validateEmail(email) && validatePswd(pswd)){
//validate entire form and render errors
}
However, the above will not execute the validatePswd method if the first method validateEmail returns false.
Is there a way to check if both values are true and run both methods? Having JavaScript run both methods would be a breeze in some cases.
You can execute them in an array and then accumulate the result with && by reduce function.
const validateEmail = value => {
if(value.includes('#')){
//setShowEmailError(false);
return true;
}
//setShowEmailError(true);
console.log('wrong email')
return false;
};
const validatePswd = value => {
if(value !== ''){
//setShowPswdError(false);
return true;
}
// setShowPswdError(true);
console.log('wrong password');
return false;
};
// you can execute any number of validations within the array.
const result = [validateEmail('something'), validatePswd('')].reduce((acc, f) => acc && f, true);
console.log(result)
UPDATE
Or as #lux suggested using every method.
const validateEmail = value => {
if(value.includes('#')){
//setShowEmailError(false);
return true;
}
//setShowEmailError(true);
console.log('wrong email')
return false;
};
const validatePswd = value => {
if(value !== ''){
//setShowPswdError(false);
return true;
}
// setShowPswdError(true);
console.log('wrong password');
return false;
};
// you can execute any number of validations within the array.
const result = [validateEmail('something'), validatePswd('')].every(r => r);
console.log(result)
I don't know if you are looking for something like this:
const valEmail = validateEmail(email);
const valPsw = validatePswd(pswd);
if(valEmail && valPsw ){
//validate entire form and render errors
}

Value is not changing in real time -- VueJS

I am using a JS class, I have following code:
class Field {
public Value = null;
public Items = [];
public UniqueKey = null;
public getItems() {
let items = [...this.Items];
items = items.filter((item) => {
if (item.VisibleIf) {
const matched = item.VisibleIf.match(/\$\[input:(.*?)\]/g);
if (matched?.length) {
const srv = Service.getInstance();
for (let match of matched) {
match = match.slice(8, -1);
if (srv.Fields?.length) {
let found = srv.Fields.find((x) => x.UniqueKey === match);
if (found) {
item.VisibleIf = item.VisibleIf.replace(
`$[input:${match}]`,
found.Value ?? ''
);
return JSON.parse('' + eval(item.VisibleIf));
}
}
}
}
}
return true;
});
return items;
}
public getInputTitle() {
let title = this.Title;
const matched = title.match(/\$\[input:(.*?)\]/g);
if (matched?.length && title) {
const srv = Service.getInstance();
for (let match of matched) {
match = match.slice(8, -1);
if (srv.Fields?.length) {
let found = srv.Fields.find((x) => x.UniqueKey === match);
if (found) {
title = title.replace(`$[input:${match}]`, found.Value ?? '');
}
}
}
}
return title;
}
}
Now I have a Vue component:
<div v-for="Field in Fields" :key="Field.UniqueKey">
<v-select
v-if="Field.Type == 'Select'"
:label="Field.getInputTitle()"
v-model="Field.Value"
:items="Field.getItems()"
item-text="Value"
item-value="Id"
/>
<v-input
v-else-if="Field.Type == 'Input'"
v-model="Field.Value"
:label="Field.getInputTitle()"
/>
</div>
// JS
const srv = Service.getInstance();
Fields = srv.getFields(); // <- API call will be there.
So basically, data comes from an API, having Title as Input $[input:uniqueKey], in a component I am looping over the data and generating the fields. See getInputTitle function in Field class, it works very well. All the fields which are dependent on the $[input:uniqueKey] are changing when I start typing into that field on which other fields are dependent.
Now I have pretty much same concept in the getItems function, so basically, what I want to do is whenever I type into a field and that field exists in the VisibleIf on the Items, the VisibleIf will be like '$[input:uniqueKey] < 1', or any other valid JavaScript expression which can be solved by eval function. But the getItems function is called only 1st time when page gets loaded, on the other hand the getInputTitle function which is pretty much same, gets called every time when I type into the field.
I tried to explain at my best, I will provide any necessary information if needed.
Any solution will be appreciated. Thanks.
You are updating the Object itself in here:
item.VisibleIf = item.VisibleIf.replace( `$[input:${match}]`, found.Value ?? '' );
Even though you tried to copy the array, but you have done shallow copy of the object in here: let items = [...this.Config.Items];
I suggest the following solution:
const visibleIf = item.VisibleIf.replace(
`$[input:${match}]`,
found.Value ?? ''
);
const val = '' + helpers.evalExp('' + visibleIf);
if (helpers.isJSON(val)) {
return JSON.parse(val);
}
Means instead of changing the VisibleIf object, just store it into the variable and just use that.
I hope that it will fix your issue. Let me know if it works.

validate multiple variables without writing multiple if else statements

Lets say we have 3 variables and I want to check them empty or not without using multiple if else blocks.
let firstName = "adem"
let lastName = "corona"
let email = "adamcorons#gmai.com"
If(firstName === " " && lastName !== " " && email !== " "){
Console.log("first name empty")
} else if .......
What is the best way of solving this?
Thanks a lot
You can avoid chained if-else stataments by returning directly from an if statement. For example, you can have a function such as this one:
function isInputValid({ firstName, lastName, email }) {
if (firstName === '') {
return false;
}
if (lastName === '') {
return false;
}
if (email === '') {
return false;
}
return true;
}
console.log(isInputValid({ firstName: 'adem', lastName: 'corona', email: 'adamcorons#gmai.com' }));
console.log(isInputValid({ firstName: 'adem', lastName: '', email: 'adamcorons#gmai.com' }));
Instead of a boolean value, you could also return an object containing an error message, so you can point out which field is missing.
You could try looping over a required array of fields like this to make it short and flexible:
const validate = (values, required) = {
let errors = {}
required.map(field => {
if (values[field] == '') {
errors[field] = 'Required';
}
}
return errors;
}
const required = ['firstName', 'lastName', 'email'];
const values = {
firstName = '',
lastname = 'test',
email = ''
}
const errors = validate(values, required);
console.log(errors);
// errors = { firstName: 'Required', email: 'Required' }
Making the values an object instead of individual parameters makes it possible to access them dynamically in a loop. This might work depending on what your needs and requirements are.
Then to check if errors exist, just see if the size of the object is 0 by converting it to an array:
if (Object.keys(errors).length == 0) {
// No errors, continue as valid
} else {
// There are errors, handle them as needed.
}
Another great way is to use YUP. It is possible to provide validation anywhere. I always use it. Here is an example.
const schema = Yup.object({
last_name: Yup.string()
.when('first_name', {
is: true,
then: Yup.string().required().label("Last Name"),
}),
});
You can use a similar as following. It also has many other amenities. You can find all the information from the official website below.
https://github.com/jquense/yup
I guess you can try something like this
// declare vars where you should, and add the vars you want to test in an object
let result = [];
let toTest= {
firstName = "adem",
lastName = "corona",
email: "adamcorons#gmai.com",
}
// declare this function to test your values
function testValue(){
Object.keys(toTest).map(key => {
if(!toTest[key]){
result.push(key);
}
}
}
// where you need, call your function to test your strings
testValue();
// in your result array you will have the keys of all the empty vars in your object
Note: if its for field validation you have some plugins like Yup(if Formik) or validate.js that are great for it ! have a look ! https://validatejs.org/
EDIT: Changed the response to an array so you can have all the results. I recommande you to set result as an object {key: errorMessage, ...} so its easier for you to use him after (ex: call the error.nameOfInput in your form to display the error.
EDIT2:
With object result would look like this
// declare vars where you should, and add the vars you want to test in an object
let error= {};
let toTest= {
firstName = "adem",
lastName = "corona",
email: "adamcorons#gmai.com",
}
// declare this function to test your values
function testValue(){
Object.keys(toTest).map(key => {
if(!toTest[key]){
error[key]={`${key} cannot be empty`};
}
}
}
// where you need, call your function to test your strings
testValue();
// in your render
<input name="firstName" ...props />
{error.firstName && <div className='error'>{error.firstName} </div> }
I believe you can't achieve this with plain if like in your example. You would need to have a validation schema and function that you run to validate, like most libraries do it. So you would need have some kind of initial object that couples the field names, their rules and error messages. But then you would need to use object with properties instead of plain variables. With those you cannot avoid checking them without separate if statements.
For reference check how joi or yup works. Although I do understand that using one of those might be too much for you, so I'd just check each field separately.

condense if, else JS with similar condition rules

trying to find a way to condense this. wasnt sure of the best way to do it. basically if criteria is met i display an alert with a parameter that is the message. i was thinking of maybe trying it in function. this is part of a larger function react component. i was also thinking if i could find a way to condense the else if's i could use a ternary. thanks in advance for the assistance.
const handleUpdatePassword = () => {
const allFilled = !reject(passwords).length;
const passwordsMatch = newPassword === conPassword;
const isDifferent = curPassword !== newPassword;
const meetsPasswordRequirements = validatePassword();
const usesName = isUsingName();
const usesUserID = isPartOfUserID();
const isValidPassword = meetsPasswordRequirements && isDifferent;
if (allFilled) {
if (!isDifferent) {
Alert.alert(difPassWord);
} else if (!passwordsMatch) {
Alert.alert(noMatch);
} else if (!meetsPasswordRequirements) {
Alert.alert(pasReqs);
} else if (usesName || usesUserID) {
Alert.alert(pasName);
}
} else {
Alert.alert(fieldNotComplete);
}
if (isValidPassword) {
changePasswordPost(
{
userId,
curPassword,
newPassword
},
partyId
);
}
};
You can create an array of objects for your validation rules, each containing a function which returns a boolean indicating whether that validation passes, and a string with the error message to display.
Then loop over the rules array and alert the message for the first rule that returns false. If they all return true, do the post.
You can split each if statement into a function, then chain them. For example
// here we make a closure to validate, and return a Promise
// condition can be a function
const validate = (condition, error) => ()=> new Promise((res, rej)=>{
if(condition()){
res();
}else{
rej(error);
}
});
const handleUpdatePassword = () => {
const validateFieldsComplete = validate(
()=>!reject(passwords).length,
fieldNotComplete
);
const validateDifPassword = validate(
()=> curPassword !== newPassword,
difPassWord
);
// ...
validateFieldsComplete()
.then(validateDifPassword)
.then(...)
.catch(Alert.alert)
}
It would be much cleaner with pipe. You can take a look at ramda. Or if you are intrested in functional way, you might consider using Monad.
I'd recommend DRYing up the Alert.alert part since all branches have that in common, and just come up with an expression that evaluates to the alert message. Compactness isn't always everything, but if you want it, then nested conditional operators can fit the bill. I'm also rearranging your conditions so that it can be a flat chain of if/elses:
const message
= reject(passwords).length ? fieldNotComplete
: curPassword === newPassword ? difPassWord
: newPassword !== conPassword ? noMatch
: !validatePassword() ? pasReqs
: (isUsingName() || isPartOfUserID()) ? pasName
: null;
const isValid = !message;
if (!isValid) {
Alert.alert(message);
}
(feel free to use any other sort of code formatting pattern; nested conditionals always look awkward no matter which pattern you use, IMO.)
Edit:
Also inlined conditionals which will short-circuit evaluation and make it even more compact.
I'd setup a validations object that has the tests and error messages and then loop over it. If validation fails, it'll throw the last validation error message. Using this method, you only have to maintain your tests in one place and not mess with a block of conditional statements.
const handleUpdatePassword = () => {
const validations = {
allFilled: {
test() {
return newPass && oldPass
},
error: 'Must fill out all fields'
},
correct: {
test() {
return curPass === oldPass
},
error: 'Incorrect password'
},
[...]
}
const invalid = () => {
let flag = false
for (let validation in validations) {
if (!validations[validation].test()) {
flag = validations[validation].error
}
}
return flag
}
if (invalid()) {
Alert.alert(invalid())
} else {
changePasswordPost(
{
userId,
curPass,
newPass
},
partyId
)
}
}
hi everyone this was the method i used for a solution
const messages = [
{
alertMessage: difPassWord,
displayRule: different()
},
{
alertMessage: noMatch,
displayRule: match()
},
{
alertMessage: pasReqs,
displayRule: validatePassword()
},
{
alertMessage: pasName,
displayRule: !isUsingName() || !isPartOfUserID()
}
];
if (allFilled) {
const arrayLength = messages.length;
for (let i = 0; i < arrayLength; i++) {
if (messages[i].displayRule === false) {
Alert.alert(messages[i].alertMessage);
}
}

React JS : Check Email Id is valid or not along with comma separated email id

I have input field which have prefilled email Id. I can change its value to one valid email id or multiple emailIds using comma separated. I am facing one issue is that it is not validating that the email id is valid or not. no matter it is one email id or multiple email id using comma separated
Below is the code that I have tried.
import React, {Component } from 'react'
class Email extends React.Component{
sendContactEmailId
getCommaSeparateValue
constructor(props){
super(props);
this.state = {
emailValue : "abc#gmail.com",
error:''
}
}
checkEmailInput = (value) => {
const regEx = /^([\w-\.]+#([\w-]+\.)+[\w-]{2,4})?$/;
var result = value.replace(/\s/g, "").split(/,|;/);
for(var i = 0;i < result.length;i++) {
if(!regEx.test(result[i])) {
this.setState({
error : 1,
})
}
this.setState({
error : 0,
emailValue : value,
})
}
if(this.state.emailValue.indexOf(',') > -1){
let getEmailId = this.state.emailValue.substr(0, this.state.emailValue.indexOf(','));
this.sendContactEmailId = getEmailId,
this.getCommaSeparateValue = this.state.emailValue.substr(this.state.emailValue.indexOf(",") + 1)
console.log("Send Contact Email : " , this.sendContactEmailId)
console.log("Get Comma Separate Value : " , this.getCommaSeparateValue);
this.arrayOfString = this.getCommaSeparateValue.split(',');
console.log("Array of String: " , this.arrayOfString);
}
}
changeValue = (value) => {
this.setState({
emailValue: value
});
}
render(){
return(
<div>
<input type="text" value={this.state.emailValue}
onBlur={(e) => this.checkEmailInput(e.target.value)} onChange={e => this.changeValue(e.target.value)}
/>
{
this.state.error === 1 ?
<span>Invalid Email</span> : ''
}
</div>
)
}
}
export default Email
Any help would be great.
thank you.
You are setting error to 0 everytime in the validation for loop.
You need to check for else case when setting error value to 0.
(Meaning, you need turn off the error only if regEx.test passes.)
for (var i = 0; i < result.length; i++) {
if (!regEx.test(result[i])) {
this.setState({
error: 1
});
} else {
this.setState({
error: 0,
emailValue: value
});
}
}
And also, I'd recommend you to put the positive test case in the if as it makes the code more readable and causes less cognitive load.
// Check for positive instead of negative.
// It improves the readability and
// put less cognitive load.
if (regEx.test(result[i])) {
this.setState({
error: 0,
emailValue: value
});
} else {
this.setState({
error: 1
});
}
}
Working demo
To answer the comment,
You can simply split the string and filter out empty records.
You need filter to get rid of empty strings (refer to the demo below).
Run the code snippet to see it work.
let result = "xyz#gmail.com, ".split(",").map(email => email.trim()).filter(email => email);
console.log(result);
result = "xyz#gmail.com, ".split(",").map(email => email.trim());
console.log(`result without filter`, result);
result = "xyz#gmail.com, y#y.com".split(",").map(email => email.trim()).filter(email => email);
console.log(result);
If input param is "a#gmail.com,b#gmail.com,c#123";
In order to validate email, first split with ',' and then validate the list of emails using isEmailsValid function.
const input = "a#gmail.com,b#gmail.com,c#123";
const emailList = input.split(',');
const emailValidationResult = isEmailsValid(emailList);
/*
* if result is true then the list of emails are valid
* else list of emails are not valid
*/
// Function to validate list of emails by iterating through the list.
isEmailsValid = (emailList) => {
for(let i=0; i<emailList.length; i+=1){
const regEx = /^([\w-\.]+#([\w-]+\.)+[\w-]{2,4})?$/;
if(!regEx.test(email)){
return false;
}
}
return true;
}

Categories

Resources