Way to rewrite if..else conditions - javascript

I have lots of conditions and if I wrote it with if .. else it works fine but may be hard to read for others (especially if it will grow in future). Is there any better way how to rewrite it in more readable way?
My code:
func(el: IHeadlines): boolean => {
if (el.type === 'Cars' && el.label) { return true; }
if (el.type === 'Bikes' && el.storage) {
if (el.storage.filter(el => el.id === 1).length >= 1) { return true; }
if (el.storage.filter(el => el.id === 2).length > 1) { return true; }
} else return false;
}
interface IHeadlines {
type: string;
label: string;
storage: [{id: number; name: string}]
}

If you ask me, I create a function that counts the value and the code will be much more readable.
function count(arr, tar) {
cnt = 0;
for(let val of arr) {
if(val === tar)
cnt += 1;
}
return cnt;
}
You can write your function this way, this is way more readable to me:
const func = (el) => {
if (el.type === 'Cars' && el.label)
return true;
if (el.type === 'Bikes' && el.storage)
if(count(el.storage,1) >= 1 || count(el.storage,2) > 1)
return true;
return false;
}
Or this way:
const func = (el) => {
if (el.type === 'Cars' && el.label)
return true;
if ((el.type === 'Bikes' && el.storage) &&
(count(el.storage,1) >= 1 || count(el.storage,2) > 1))
return true;
return false;
For this is much more readable, however you can change the count function to any other way you like, and I would prefer using this code even if it is longer but it is much more readable.

Have you tried using switch-case?
for example:
function(el)=>{
switch(el.type):
case 'Cars':
return true;
break;
case 'Bikes':
return true;
break;
default:
return false;
}
After this, you can perhaps put if-else before 'return' in each case.

hello, i do some optimize. I wish this can help you.
const oldFunc = (el) => {
if (el.type === 'News' && el.label) {
return true;
}
if (el.type === 'Research' && el.storage) {
if (el.storage.filter(el => el.id === 1).length >= 1) {
return true;
}
if (el.storage.filter(el => el.id === 2).length > 1) {
return true;
}
} else return false;
}
// do some optimize
const newFunc = (el) => {
let flag = false;// default return false
flag = ((el.type === 'News' && el.label) ||
((el.type === 'Research' && el.hasOwnProperty('storage')) ? el.storage.some(o=>[1,2].includes(o.id)): false)) && true;
return flag;
}
// test code
const testData = {
type: 'News',
label: 'test'
};
console.log(oldFunc(testData));
console.log(newFunc(testData));
const testData2 = {
type: 'Research',
storage: [
{
id: 1,
name: "John"
}
]
};
console.log(oldFunc(testData2));
console.log(newFunc(testData2));
// test result
// true
// true
// true
// true

Two ways come to my mind, but none of them will make your code very clear because conditions are dirty stuff.
If you return boolean, you don't need if..else blocks. Just return the conditions.
func(el) => {
return (el.type === 'Cars' && el.label) ||
(el.type === 'Bikes' &&
(el.storage?.filter(el => el.id == 1).length >= 1 ||
el.storage?.filter(el => el.id == 2).length > 1)
)
}
You can extract the group of conditions to separate functions and call them in the main function.
const checkForCars = (el) => { return el.type === 'Cars' && el.label }
const checkForBikes = (el) => { return // your conditions}
const mainFunction (el) {
return checkForCars(el) || checkForBikes(el);
}

I generally try to be as descriptive as possible. Rename func to what the function does.
For readability, you could also create a function in el called, isCar(), isBike(), hasStorage() etc etc which would encapsulate that logic. I'm not sure if that makes sense based on what you provided. You are also inline hard coding ids. It would make it clearer if el contained some const / var or something in your app had them, which described the id. You could also rename el to something descriptive. You can remove some if by doing what Guerric P said.
Even the filter functions could be moved if they were going to be reused..
const shedFilter = (el) => ...(function code here)
Then provide some comments if anything is not clear.
const STORAGE_SHED = 1;
const SOTRAGE_GARAGE = 2;
aGoodName(el) => {
if (el.isCar()) { return true; }
if (el.isBike()) {
if (el.storage.filter(el => el.id === STORAGE_SHED ).length >= 1) { return true; }
if (el.storage.filter(el => el.id === SOTRAGE_GARAGE).length > 1) { return true; }
}
else { return false };
}

Step 1: To have exactly same logic as you had - you can start with extracting the conditions/function and trying to avoid return true and return false statements. You can easily return condition itself.
const isNews = el.type === 'News' && el.label;
const isBikes = el.type === 'Bikes' && el.storage;
const storageItemsCount = (el, id) => el.storage.filter(el.id === id).length;
return isNews
|| (isBikes && (storageItemsCount(el, 1) >= 1 || storageItemsCount(el, 2) > 1)
Step 2: further I would remove "magic" id 1 and 2 values and explicitly specify what they are about, like
const BIKE1_ID = 1;
const BIKE2_ID = 2;
no we can generalise the counts check with specifying
const minBikeCounts = {
[BIKE1_ID]: 1,
[BIKE1_ID]: 2
}
const bikeCountsAreValid = el => {
return Object.entries(k)
.every(
([id, minCount]) => el.storage.filter(el => el.id === key).length >= minCount)
}
so the main flow simplified to
const isNews = el.type === 'News' && el.label;
const isBikes = el.type === 'Bikes' && el.storage;
return isNews || (isBikes && bikeCountsAreValid(el))
Step 3: We can see a pattern of "Check if type is supported with some extra check". We could extract the knowledge of supported types to separate structure. Now if we want to add new supported type we don't need to remember all if/else statements across the codebase, and just add new one here:
const supportedTypes = {
News: el => !!el.label,
Bikes: el => !!el.storage && bikeCountsAreValid(el)
}
no if statements at all in our main function:
const isSuppotedType =
(el: IHeadlines) => supportedTypes[el.type] && supportedTypes[el.type](el)

Good clean code practices suggest that you have small functions and have a self-describing code.
In your code, I would make each Boolean logic become a variable or a function.
Example 1
func(element: IHeadlines): boolean => {
const isCarWithLabel = element.type === 'Cars' && element.label;
if(isCarWithLabel){
return true;
}
const isBikeWithStorage = element.type === 'Bikes' && element.storage;
if(isBikeWithStorage){
// rest of your logic that I don't know...
}
return false;
}
Example 2
func(element: IHeadlines): boolean => {
return this.isCarWithLabel() || this.isBikeWithStorageAndSomethingElse();
}

I'll suggest you something like this:
func(el: IHeadlines): boolean {
return el.type === 'Cars' && !!el.label ||
el.type === 'Bikes' && (
!!el.storage?.find(el => el.id == 1) || el.storage?.filter(el => el.id == 2).length > 1
);
}

Related

How to drastically improve this if else if

I'm trying to improve this code from all of these else if statements to a switch or something more efficient.
renderIconType = () => {
const { icons = {} } = this.props;
const percent = courseDataStore.getComponentCompletionPercentage(this.props.linkedNodeId);
const isHovering = this.state.isHovering;
const isStarted = !!courseDataStore.getComponentCompletionPercentage(this.props.linkedNodeId);
const isCompleted = percent >= 1;
if (isStarted && isCompleted) {
return icons.completed
} else if (isStarted && isCompleted && isHovering) {
return icons.completedHover
} else if (!isStarted && !isCompleted && isHovering) {
return icons.notStartedHover
} else if (isStarted && !isCompleted && isHovering) {
return icons.startedHover
} else if (isStarted && !isCompleted && !isHovering) {
return icons.started
} else if (!isStarted && !isCompleted && !isHovering) {
return icons.notStarted
}
}
This currently works for my intents and purposes but pains me to look at it lol. I tried a switch statement but that doesn't work.

VUE3 compounding filters not working as intended

so I'm trying to filter results using multiple filter options. ive tried a lot of different things and it works as intended with just 2 filters applied, but if i apply 3 or more filters to a search it will add more results to the search that don't match all the criteria.
meaning the more filters applied, the narrower the results should get distilled down to.
here is a gif showing what happens. https://imgur.com/a/gAX3ntA
here is the code I'm currently using. its bloated beyond all hell as I would have to think there is a simpler way to do this with compounding filters. plus if I want to add more filter options this way of doing things would quickly get insanely over complicated. please tell me there is a simpler way to do this lol.
I'm using VUE 3 with the composition API.
const months = computed(() => {
return documents.value.filter((plants) =>
plants.months.includes(month.value)
);
});
const plantType = computed(() => {
return documents.value.filter(
(plants) => plants.plantType == plantT.value
);
});
const Zone = computed(() => {
return documents.value.filter((plants) =>
plants.Zone.includes(getZone.value)
);
});
const toxicPets = computed(() => {
return documents.value.filter((plants) =>
plants.toxicPets.includes(toxic.value)
);
});
const Combined = computed(() => {
gettingThree = false;
return documents.value.filter(
(plants) =>
plants.Zone.includes(getZone.value) &&
plants.months.includes(month.value) &&
plants.plantType == plantT.value &&
plants.toxicPets.includes(toxic.value)
);
});
const Combined2 = computed(() => {
gettingTwo = true;
gettingThree = false;
return documents.value.filter(
(plants) =>
(plants.Zone.includes(getZone.value) &&
plants.months.includes(month.value)) ||
(plants.Zone.includes(getZone.value) &&
plants.plantType == plantT.value) ||
(plants.Zone.includes(getZone.value) &&
plants.toxicPets.includes(toxic.value)) ||
(plants.months.includes(month.value) &&
plants.toxicPets.includes(toxic.value)) ||
(plants.plantType == plantT.value &&
plants.toxicPets.includes(toxic.value)) ||
(plants.plantType == plantT.value &&
plants.months.includes(month.value))
);
});
const Combined3 = computed(() => {
gettingTwo = false;
gettingThree = true;
return documents.value.filter(
(plants) =>
(plants.Zone.includes(getZone.value) &&
plants.plantType == plantT.value &&
plants.months.includes(month.value)) ||
(plants.Zone.includes(getZone.value) &&
plants.toxicPets.includes(toxic.value) &&
plants.plantType == plantT.value) ||
(plants.Zone.includes(getZone.value) &&
plants.months.includes(month.value) &&
plants.toxicPets.includes(toxic.value)) ||
(plants.plantType == plantT.value &&
plants.months.includes(month.value) &&
plants.toxicPets.includes(toxic.value))
);
});
const searchMatch = computed(() => {
if (Combined.value.length > 0) {
console.log("getting 4");
return Combined.value.filter(
(plant) =>
plant.plantName.toLowerCase().indexOf(search.value.toLowerCase()) !=
-1
);
}
if (Combined3.value.length > 0 && gettingTwo == false) {
console.log("getting 3");
return Combined3.value.filter(
(plant) =>
plant.plantName.toLowerCase().indexOf(search.value.toLowerCase()) !=
-1
);
}
if (Combined2.value.length > 0 && gettingThree == false) {
console.log("getting 2");
return Combined2.value.filter(
(plant) =>
plant.plantName.toLowerCase().indexOf(search.value.toLowerCase()) !=
-1
);
}
if (
month.value !== null &&
getZone.value == null &&
toxic.value == null &&
plantT.value == null
) {
return months.value.filter(
(plant) =>
plant.plantName.toLowerCase().indexOf(search.value.toLowerCase()) !=
-1
);
}
if (
getZone.value !== null &&
plantT.value == null &&
month.value == null &&
toxic.value == null
) {
return Zone.value.filter(
(plant) =>
plant.plantName.toLowerCase().indexOf(search.value.toLowerCase()) !=
-1
);
}
if (
plantT.value !== null &&
month.value == null &&
getZone.value == null &&
toxic.value == null
) {
return plantType.value.filter(
(plant) =>
plant.plantName.toLowerCase().indexOf(search.value.toLowerCase()) !=
-1
);
}
if (
toxic.value !== null &&
plantT.value == null &&
month.value == null &&
getZone.value == null
) {
return toxicPets.value.filter(
(plant) =>
plant.plantName.toLowerCase().indexOf(search.value.toLowerCase()) !=
-1
);
}
return documents.value.filter((plant) => {
return (
plant.plantName.toLowerCase().indexOf(search.value.toLowerCase()) !=
-1
);
});
});
holy crap I figured it out on my own.
my solution is now WAY simpler than the solution I was working with in the OP and gives the desired results. see the gif to see how it works now.
gif - https://imgur.com/mY8XksT
here is the code I came up with.
//if a variable is null move on to the next filter or if the variable has a
//value then filter the results to include the value and move to the next
//filter. rinse and repeat for each filter.
const Combined = computed(() => {
return documents.value
.filter((plants) => {
return getZone.value == null || plants.Zone.includes(getZone.value);
})
.filter((plants) => {
return month.value == null || plants.months.includes(month.value);
})
.filter((plants) => {
return (
plantT.value == null || plants.plantType.includes(plantT.value)
);
})
.filter((plants) => {
return toxic.value == null || plants.toxicPets.includes(toxic.value);
});
});
//below takes the Combined filters property from above and runs it though another computed property to allow the user to type search in
//a input field. im running the search results through an array of multiple names per item since plants tend to have more than one name. so
//the user can search a varity of different names and still get a result that is correct.
const searchMatch = computed(() => {
return Combined.value.filter((plant) => {
let arr_lower = plant.otherNames.map(
(item) => item.toLowerCase().indexOf(search.value.toLowerCase()) != -1
); //this function just returns true of false if the item matches the search results.
return arr_lower.includes(true); //so here im just returning the arr_lower variable if it includes true.
});
});

More efficient or concise method for this function?

I have a data set of exercises that can be done when working out, in JSON format. I'm implementing a filtering system on this data, which has three different types of filters users can apply: sort filter, (which takes the current set of exercises and orders them by name a-z or vice versa), muscle group (exercises that fall under the same targeted muscle group) and training program (exercises that fall under the same training program).
Users should be able to select any of the filters and it will apply it straight away, but not all 3 types of filters have to be applied. Therefore, I have come up with the following function in JS:
function applyFilters(filters) {
const sort = filters[0];
const muscles = filters[1];
const programs = filters[2];
const exerciseJSONData = "exercises.json";
$.getJSON(exerciseJSONData, {
format: JSON,
}).done(function (result) {
let filteredArr = [];
if (muscles.length === 0 && programs.length === 0) {
filteredArr = result;
} else if (muscles.length !== 0 && programs.length === 0) {
$.each($(result), function (key, val) {
if (muscles.some((item) => val.MainMuscleGroup.indexOf(item) >= 0)) {
filteredArr.push(this);
}
});
} else if (muscles.length === 0 && programs.length !== 0) {
$.each($(result), function (key, val) {
if (programs.some((item) => val.TrainingProgram.indexOf(item) >= 0)) {
filteredArr.push(this);
}
});
} else {
$.each($(result), function (key, val) {
if (
muscles.some((item) => val.MainMuscleGroup.indexOf(item) >= 0) &&
programs.some((item) => val.TrainingProgram.indexOf(item) >= 0)
) {
filteredArr.push(this);
}
});
}
$("#number-of-exercises").text(filteredArr.length + " Exercises Found");
$("#exercises").empty();
createExerciseHTML(filteredArr);
});
}
This function works as intended, however, I think the readability of the if else statement could be hard to understand for others and breaks the rule of not repeating code. I am wondering if there is a more concise and efficient way of coding this function? Thanks in advance.
P.S. Sorry if there was too much info at the beginning of this post, I wanted to give some context to avoid any confusion readers may have.
You can start by taking the $each out and keep the if in the each.
function applyFilters(filters) {
const sort = filters[0];
const muscles = filters[1];
const programs = filters[2];
const exerciseJSONData = "exercises.json";
$.getJSON(exerciseJSONData, {
format: JSON,
}).done(function (result) {
let filteredArr = [];
var hasMuschle = muscles.length > 0 ? 1 : 0;
var hasProgram = programs.length > 0 ? 1 : 0;
var muscleAndProgram = hasMuschle - hasProgram;
$.each($(result), function (key, val) {
if (muscles.length === 0 && programs.length === 0) {
filteredArr = result;
} else if (muscles.length !== 0 && programs.length === 0) {
if (muscles.some((item) => val.MainMuscleGroup.indexOf(item) >= 0)) {
filteredArr.push(this);
}
} else if (muscles.length === 0 && programs.length !== 0) {
if (programs.some((item) => val.TrainingProgram.indexOf(item) >= 0)) {
filteredArr.push(this);
}
} else {
if (
muscles.some((item) => val.MainMuscleGroup.indexOf(item) >= 0) &&
programs.some((item) => val.TrainingProgram.indexOf(item) >= 0)
) {
filteredArr.push(this);
}
}
})
$("#number-of-exercises").text(filteredArr.length + " Exercises Found");
$("#exercises").empty();
createExerciseHTML(filteredArr);
});
}

DRY - Typescript. How can I use DRY principles to avoid duplication of these 2 getters

I know that below two getters are duplicates and could be consolidated and written in a better way. Could any one please help me come up with a way to consolidate these:-
isEqual here is a lodash library to compare two objects.
state in here is an injected state which I am picking the objects from.
public get isUpperModified(): boolean {
if (!this.isUpperAvailable) {
return false;
}
if (
(this.orders.upperPreference.type === '1' &&
this.state.fetchedData.upperPreference.type === '1') ||
(this.orders.upperPreference.type === 'UPPER' &&
this.state.fetchedData.upperPreference.type === 'UPPER')
) {
return false;
}
if (!isEqual(this.orders.upperPreference, this.state.fetchedData.upperPreference)) {
return true;
}
return false;
}
public get isLowerModified(): boolean {
if (!this.isLowerAvailable) {
return false;
}
if (
(this.orders.lowerPreference.type === '1' &&
this.state.fetchedData.lowerPreference.type === '1') ||
(this.orders.lowerPreference.type === 'LOWER' &&
this.state.fetchedData.lowerPreference.type === 'LOWER')
) {
return false;
}
if (!isEqual(this.orders.lowerPreference, this.state.fetchedData.lowerPreference)) {
return true;
}
return false;
}
There are more than 1 way to achieve this.
You can create a new function isModified(type: string) and pass upper or lower as an argument.
Hope this helps
public get isUpperModified(): boolean {
return this.isModified('upper');
}
public get isLowerModified(): boolean {
return this.isModified('lower');
}
private isModified(type: 'lower' | 'upper'): boolean {
const available = type === 'lower' ? this.isLowerAvailable : this.isUpperAvailable;
const order = type === 'lower' ? this.orders.lowerPreference : this.orders.upperPreference;
const state = type === 'lower' ? this.state.fetchedData.lowerPreference : this.state.fetchedData.upperPreference;
if (!available) {
return false;
}
if (
(order.type === '1' &&
state.type === '1') ||
(order.type === type.toUpperCase() &&
state.type === type.toUpperCase())
) {
return false;
}
if (!isEqual(order, state)) {
return true;
}
return false;
}
I would do it something like this
public get isModified(type: 'lower' | 'upper'): boolean {
const isAvailable = type === "lower" ? this.isLowerAvailable : this.isUpperAvailable
const preference = type === "lower" ? "lowerPreference" : "upperPreference";
if (!isAvailable) {
return false;
}
if (
(this.orders[preference].type === '1' &&
this.state.fetchedData[preference].type === '1') ||
(this.orders[preference].type === 'LOWER' &&
this.state.fetchedData[preference].type === 'LOWER')
) {
return false;
}
if (!isEqual(this.orders[preference], this.state.fetchedData[preference])) {
return true;
}
return false;
}
Then while calling this method
use isModified("upper") instead of isUpperModified
and
use isModified("lower") instead of isLowerModified

Shorthand JS Condition

I need a bit of assistance with this shorthand condition. My attempt at it so far is becoming a bit of a challenge and cant seem to make it more readable. I believe it was short circuited from minifying.
if (a === !0 || perSearch.rates.fy2) {
e();
} else if ( perSearch.rates.fy1.multiple || perSearch.rates.fy2.multiple ){
calculateRates();
} else {
perSearch.rates.fy1.multiple;
}
From this terse expression:
a === !0 || (perSearch.rates.fy2 ? perSearch.rates.fy1.multiple || perSearch.rates.fy2.multiple ? e() : calculateRates() : perSearch.rates.fy1.multiple ? e() : calculateRates())
Your expression corresponds to
if (a === !0) {
} else {
if (perSearch.rates.fy2) {
if (perSearch.rates.fy1.multiple || perSearch.rates.fy2.multiple) {
e();
} else {
calculateRates();
}
} else {
if (perSearch.rates.fy1.multiple) {
e();
} else {
calculateRates();
}
}
}
which can be simplified to
if (a !== true) {
if (perSearch.rates.fy1.multiple || (perSearch.rates.fy2 && perSearch.rates.fy2.multiple)) {
e();
} else {
calculateRates();
}
}
This should be it:
if (a !== false) {
if (perSearch.rates.fy2) {
if (!perSearch.rates.fy1.multiple) {
if (perSearch.rates.fy2.multiple) {
e()
}
else {
calculateRates()
}
}
}
else {
if (perSearch.rates.fy1.multiple) {
e()
}
else {
calculateRates()
}
}
}
Is this a return of some sort (return a || result), or a large conditional (a is true or this other code evaluates true)?. Either way I would tend to attack it in stages, block code, then abstract, repeat until the code looks manageable.
Block
Block out your conditionals to make it easier to read.
(
a === !0
|| (
perSearch.rates.fy2
? (perSearch.rates.fy1.multiple || (perSearch.rates.fy2.multiple ? e() : calculateRates()))
: (perSearch.rates.fy1.multiple ? e() : calculateRates())
)
)
Abstract
Abstract out some any big easy repetitions of logic.
const x = rates => ( (rates) ? e() : calculateRates() );
(
a === true || (perSearch.rates.fy2)
? ((perSearch.rates.fy1.multiple) || x(perSearch.rates.fy2.multiple))
: x(perSearch.rates.fy1.multiple)
)
Continue
Work into the code to separate out the conditionals.
const calc = rates => ((rates) ? e() : calculateRates());
const compare = rates => {
let fy1 = (rates.hasOwnProperty('fy1')) ? rates.fy1 : false;
let fy2 = (rates.hasOwnProperty('fy2')) ? rates.fy2 : false;
if (fy2) {
if (fy1.multiple) {
return fy1.multiple;
}
return calc(fy2.multiple);
} else {
return calc(fy1.multiple);
}
}
a === true || compare(perSearch.rates);
Edit (more continue!)
Looking at this again I think it would benefit from some early returns.
Look at conditional for simplification.
If fy2 and if fy1.multiple {return fy1.multiple}
If not fy2 {return fy1.multiple}
If fy2 and not fy1.multiple {return fy2.multiple}
const calc = rates => ((rates) ? e() : calculateRates());
const compare = rates => {
let fy1 = (rates.hasOwnProperty('fy1')) ? rates.fy1 : false;
let fy2 = (rates.hasOwnProperty('fy2')) ? rates.fy2 : false;
// consolidate conditions leading to same place.
if (!fy2 || (fy2 && fy1 && fy1.multiple)) {
return calc(fy1.multiple);
}
return calc(fy2.multiple);
}
a === true || compare(perSearch.rates);

Categories

Resources