condense if, else JS with similar condition rules - javascript

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);
}
}

Related

Alternatives to a large if/switch with multiple cases in JavaScript?

I have multiple if statements (50/60) inside a loop.What would be the best approach to perform this actions, switch or map lookup? How can i implement map lockups for the following examples?
errors.forEach((e) => {
if (e.field === 'firstName') {
this.hasErrorFirstName = true;
this.msgFirstName = e.error;
}
if (e.field === 'lastName') {
this.hasErrorLastName = true;
this.msgLastName = e.error;
}
if (e.field === 'middleName') {
this.hasErrorMiddleName = true;
this.msgMiddleName = e.error;
}
if (e.field === 'address') {
this.hasErrorAddress = true;
this.msgAddress = e.error;
}
}
You can do some thing like below
const obj = {
firstName: ['hasErrorFirstName', 'msgFirstName'],
lastName: ['hasErrorLastName', 'msgLastName'],
}
errors.forEach(e => {
if (Object.keys(obj).includes(e.field)) {
const [has, msg] = obj[e.field];
this[has] = true;
this[msg] = e.error
}
})
This indicates that data is stored in inefficient way. There may be no need to have separate hasErrorFirstName and msgFirstName keys, because error message can be forced to be truthy and be an indicator that there's an error. And there is no need to have keys that are named differently than respective fields. In this case an array can be mapped to a map of error messages:
Object.fromEntries(errors.map(e => [e.field, e.error]))

How to break the for loop using state

I have code as below.
I need to break the loop when first match is found.
const [isCodeValid, setIsCodeValid] = useState(false);
for (let i = 0; i < properyIds.length; i++) {
if (isCodeValid) {
break; // this breaks it but had to click twice so state would update
}
if (!isCodeValid) {
firestore().collection(`properties`)
.doc(`${properyIds[i]}`)
.collection('companies').get()
.then(companies => {
companies.forEach(company => {
if (_.trim(company.data().registrationCode) === _.trim(registrationCode.toUpperCase())) {
console.log("should break here")
// updating state like this wont take effect right away
// it shows true on second time click. so user need to click twice right now.
setIsCodeValid(true);
}
});
})
}
}
state won't update right away so if (!isCodeValid) only works on second click.
Once I find match I need to update state or variable so I can break the for loop.
I tried to use a variable but its value also not changing in final if condition, I wonder what is the reason? can anyone please explain ?
You should try and rewrite your code such that you will always call setIsCodeValid(value) once. In your case it could be called multiple times and it might not get called at all
const [isCodeValid, setIsCodeValid] = useState(false);
function checkForValidCode() {
// map to an array of promises for companies[]
const companiesPromises = properyIds.map(propertyId =>
firestore()
.collection(`properties`)
.doc(propertyId)
.collection('companies').get())
Promise.all(companiesPromises)
// flatten the 2d array to single array, re-create to JS array because of firestores internal types?
.then(companiesArray => [...companiesArray].flatMap(v => v))
// go through all companies to find a match
.then(companies =>
companies.find(
company => _.trim(company.data().registrationCode) === _.trim(registrationCode.toUpperCase())
))
.then(foundCompany => {
// code is valid if we found a matching company
setIsCodeValue(foundCompany !== undefined)
})
}
Try something like this:
import { useState } from 'react';
function YourComponent({ properyIds }) {
const [isCodeValid, setIsCodeValid] = useState(false);
async function handleSignupClick() {
if (isCodeValid) {
return;
}
for (let i = 0; i < properyIds.length; i++) {
const companies = await firestore()
.collection(`properties`)
.doc(`${properyIds[i]}`)
.collection('companies')
.get();
for (const company of companies.docs) {
if (_.trim(company.data().registrationCode) === _.trim(registrationCode.toUpperCase())) {
setIsCodeValid(true);
return;
}
}
}
}
return (<button onClick={handleSignupClick}>Sign Up</button>);
}
If you await these checks, that will allow you to sequentially loop and break out with a simple return, something you can't do inside of a callback. Note that if this is doing database queries, you should probably show waiting feedback while this is taking place so the user knows that clicking did something.
Update:
You may want to do all these checks in parallel if feasible so the user doesn't have to wait. Depends on your situation. Here's how you'd do that.
async function handleSignupClick() {
if (isCodeValid) {
return;
}
const allCompanies = await Promise.all(
properyIds.map(id => firestore()
.collection(`properties`)
.doc(`${properyIds[i]}`)
.collection('companies')
.get()
)
);
setIsCodeValid(
allCompanies.some(companiesSnapshot =>
companiesSnapshot.docs.some(company =>
_.trim(company.data().registrationCode) === _.trim(registrationCode.toUpperCase())
)
)
);
}
Can you not break it after setIsCodeValid(true);?
Use some:
companies.some(company => {
return _.trim(company.data().registrationCode) === _.trim(registrationCode.toUpperCase());
});
If some and forEach are not available then companies is not an array but an array-like object. To iterate through those, we can use for of loop:
for (const company of companies){
if (_.trim(company.data().registrationCode) === _.trim(registrationCode.toUpperCase())) {
// do something
break;
}
}
I tired below and it worked for me to break the loop.
I declared and tried to change this variable let codeValid and it was just not updating its value when match found. (not sure why)
But all of a sudden I tried and it just works.
I didnt change any actual code except for variable.
let codeValid = false;
let userInformation = []
for (let i = 0; i < properties.length; i++) {
console.log("called")
const companies = await firestore().collection(`properties`)
.doc(`${properties[i].id}`)
.collection('companies').get()
.then(companies => {
companies.forEach(company => {
if (_.trim(company.data().registrationCode) === _.trim(registrationCode.toUpperCase())) {
// a += 1;
codeValid = true;
userInformation.registrationCode = registrationCode.toUpperCase();
userInformation.companyName = company.data().companyName;
userInformation.propertyName = properties[i].propertyName;
}
});
})
if (codeValid) {
break;
}
}

Conditional validation using single piece of code - AngularJS

The code contains two functions. First one is defined as follows
scope.validateContactName = function() {
scope.address.invalidName = false;
if (!scope.address.name) {
scope.address.invalidName = true;
}
}
which is invoked by the function validateContactName();
Now i have another function
scope.validateContactPhone = function() {
scope.address.invalidPhone = false;
if (!scope.address.phone) {
scope.address.invalidPhone = true;
}
}
which is invoked by the function validateContactPhone();
Instead of two functions, is there a way i can use a single function and do conditional validation?
Something like
validateContactInfo('name');
function validateContactInfo(attr) {
//do validation based on the attribute
// There is only one single piece of code for both conditions
}
Maybe smth like this could work:
scope.validateField = function(field, errorField) {
scope.address[errorField] = false;
if (!scope.address[field]) {
scope.address[errorField] = true;
}
}
Or a shorter version:
scope.validateField = function(field, errorField) {
scope.address[errorField] = !scope.address[field];
}
I would suggest something like this(ES6):
scope.address = [
{
type: "phone",
invalid: false
},
{
type: "name",
invalid: false
}
];
const validate = type => {
let data = scope.address.find(a => a.type === type);
if(!data.type) {
data.invalid = true;
}
};
validate("phone");
validate("name");
Assuming contact information is used in a form to get input from the user. I would recommend to use angular's own form validation
If it is not the case, here is a generic way of checking if values exists in a object. Which you can add in project''s utilities
const contactInfo = {
name: 'My name',
phone: '123123123',
address: ''
}
function validateExistence(obj){
const emptyKeys = [];
for(let key in obj){
if(!obj[key]) emptyKeys.push(key)
}
return emptyKeys
}
console.log(validateExistence(contactInfo));

Improving readability of multiple if statements with common pattern

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, ...);
}
}

How do I ensure an array has no null values?

I would like test my Array (input value) before submit my form.
My array with value :
const fields = [
this.state.workshopSelected,
this.state.countrySelected,
this.state.productionTypeSelected,
this.state.numEmployeesSelected,
this.state.startAt
];
I've try this :
_.forEach(fields, (field) => {
if (field === null) {
return false;
}
});
alert('Can submit !');
...
I think my problem is because i don't use Promise. I've try to test with Promise.all(fields).then(());, but i'm always in then.
Anyone have idea ?
Thank you :)
The problem is that even though you're terminating the lodash _.forEach loop early, you don't do anything else with the information that you had a null entry.
Instead of lodash's _.forEach, I'd use the built-in Array#includes (fairly new) or Array#indexOf to find out if any of the entries is null:
if (fields.includes(null)) { // or if (fields.indexOf(null) != -1)
// At least one was null
} else {
// All were non-null
alert('Can submit !');
}
For more complex tests, you can use Array#some which lets you provide a callback for the test.
Live example with indexOf:
const state = {
workshopSelected: [],
countrySelected: [],
productionTypeSelected: [],
numEmployeesSelected: [],
startAt: []
};
const fields = [
state.workshopSelected,
state.countrySelected,
state.productionTypeSelected,
state.numEmployeesSelected,
state.startAt
];
if (fields.indexOf(null) != -1) {
console.log("Before: At least one was null");
} else {
console.log("Before: None were null");
}
fields[2] = null;
if (fields.indexOf(null) != -1) {
console.log("After: At least one was null");
} else {
console.log("After: None were null");
}
You do not need to use promises unless there is an asynchronous operation (for example if you are getting that array from your server).
If you already have that array you can do something like:
// Using lodash/underscore
var isValid = _.every(fields, (field) => (field!==null)}
// OR using the Array.every method
var isValid = fields.every((field)=>(field!==null))
// Or using vanilla JS only
function checkArray(array){
for(var i = 0; i < array.length ; i ++){
if(array[i]===null){
return false;
}
}
return true;
}
var isValid = checkArray(fields);
// After you get that value, you can execute your alert based on it
if(!isValid){
alert('Something went wrong..');
}
Try this simple snippet
var isAllowedToSubmit = true;
_.forEach(fields, (field) => {
if (!field) {
isAllowedToSubmit = false;
}
});
if(isAllowedToSubmit)
alert('Can submit !');
You can do that without library:
if (fields.some(field => field === null)) {
alert('Cannot submit');
} else {
alert('Can submit');
}
You don't need to use lodash, you can do this in simple vanilla javascript. Simply iterate over each field and if an error occurs set your errors bool to true
let errors = false;
fields.forEach(field) => {
if(field === null || field === '') {
errors = true;
}
});
if (!errors) {
alert('Yay no errors, now you can submit');
}
For an es6 you can use.
const hasNoError = fields.every((field, index, selfArray) => field !== null);
if (!hasNoError) {
alert('yay It works');
};
Have a look at Array.every documentation Array every MDN documentation

Categories

Resources