Based on array elements returning different output - javascript

i have caluclateAcess function which takes an array as input ,
the array will have max of three elements and min of one element
so the possible elements it can come is create, read and update
so its basically 2^3 which means 8 probabablities can come so
i am listing down all the probabablities and returning a value
i will list down the possible input and what output should i return. Array of empty wont be comming which means false, false, false
create => 'CreateAndRead'
read => 'Read'
update => 'UpdateAndRead'
create,read => 'CreateAndRead'
update, read => 'UpdateAndRead'
create, update => 'ALL'
create,read, update => 'ALL'
so i have written the below function is there any better way to achieve this
let READ = 'read';
let CREATE = 'create';
let UPDATE = 'update';
const caluclateAcess = (inputArray) => {
if (
(inputArray.indexOf(READ) > -1
&& inputArray.indexOf(UPDATE) > -1
&& inputArray.indexOf(CREATE) > -1)
||
(
inputArray.indexOf(UPDATE) > -1
&& inputArray.indexOf(CREATE) > -1
)
) {
return 'ALL';
}
if (
(inputArray.indexOf(CREATE) > -1
&& inputArray.indexOf(READ) > -1)
|| (inputArray.indexOf(CREATE) > -1
&& (inputArray.indexOf(READ) === -1 && inputArray.indexOf(UPDATE) === -1))
) {
return 'CreateAndRead';
}
if (
(inputArray.indexOf(UPDATE) > -1
&& inputArray.indexOf(READ) > -1)
|| (inputArray.indexOf(UPDATE) > -1
&& (inputArray.indexOf(READ) === -1 && inputArray.indexOf(CREATE) === -1))
) {
return 'UpdateAndRead';
}
if (inputArray.indexOf(READ) > -1) {
return 'Read';
}
};
Any help appreciated

You can start off by removing the repeat inputArray.indexOf() calls - it makes everything harder to read. It's simpler to check these once only:
const hasCreate = inputArray.indexOf(CREATE) > -1;
const hasUpdate = inputArray.indexOf(UPDATE) > -1;
const hasRead = inputArray.indexOf(READ) > -1;
Second, the rules you set up show that you have three access properties - read, update, and create, each one may imply another. Looking at those, it becomes clear that you have the following relationships:
read => read
update => update, read
create => create, read
Thus, update and create are actually compound. This means that the initial check can be modified to account for these:
const hasCreate = inputArray.indexOf(CREATE) > -1;
const hasUpdate = inputArray.indexOf(UPDATE) > -1;
const hasRead = hasCreate || hasUpdate || inputArray.indexOf(READ) > -1;
This avoids having the checks for if somebody has read or update.
As an extra note, you can just use Array#includes instead of checking the index.
With that said, the read right is pretty much inconsequential. It matters if it's the only one available, in all other cases it's pretty much ignored or assumed to be present. Still, it's useful to model the implied rules - perhaps this can change in the future.
Finally, the logic is too complex. There are only four possible final states and as mentioned above, read is not even used for most of them. Here is the updated code that does all the checks. I've added another state called "None" for the case when there are no permissions at all. Even if it's not possible for this to happen, I find it easier to just have it and it be unused than omit it:
const caluclateAcess = (inputArray) => {
const hasCreate = inputArray.includes('create');
const hasUpdate = inputArray.includes('update');
const hasRead = hasCreate || hasUpdate || inputArray.includes('read');
if (hasCreate === true && hasUpdate === true) return "ALL";
if (hasCreate) return "CreateAndRead";
if (hasUpdate) return "UpdateAndRead";
if (hasRead) return "Read";
return "None";
};
console.log("create =>", caluclateAcess(["create"]) );
console.log("read =>", caluclateAcess(["read"]) );
console.log("update =>", caluclateAcess(["update"]) );
console.log("create, read =>", caluclateAcess(["create", "read"]) );
console.log("update, read=>", caluclateAcess(["update", "read"]) );
console.log("create, update =>", caluclateAcess(["create", "update"]) );
console.log("create, read, update =>", caluclateAcess(["create", "read", "update"]));
console.log("<nothing> =>", caluclateAcess([]) );
The READ, CREATE, and UPDATE variables are not needed, since they are only used once, so I inlined them to shorten the code even more.
However, if there really is no possibility for an empty array of access properties, then that implies that the read access is completely inconsequential. It's impossible to not have it. So, checking for it can be entirely skipped and the default return value of the function can be changed from "None" to "Read". However, my personal preference is to keep the check - it doesn't hurt any of the functionality and the implementation produces no deviation from the specs. If the specs change in the future, or there is some sort of bug, it's probably better to not automatically grant read access.

Related

How to optimize multiple variable check

const {
service,
customer,
company,
parking,
aircraftType,
aircraft,
endPlan,
startPlan,
heatingPointsMasterCodes,
lavatoryType,
passengersCategory } = formValues;
useEffect(() => {
customer &&
company &&
(parking || service === ReferenceCodesOfServicesEnum.ProvisioningMinibus) &&
aircraftType &&
aircraft &&
endPlan &&
startPlan &&
(heatingPointsMasterCodes ||
lavatoryType ||
passengersCategory ||
formValues[DocumentItemNamesEnum.WaterSystemMaintenance] ||
service === ReferenceCodesOfServicesEnum.AircraftCooling)
? setDisabled(false)
: setDisabled(true);
}, [formValues]);
So my question is, how to optimize or reduce variable check for true value?
First i get variables with destructing from object, then check same variables for true value.
I think i can somehow optimize this, but dont know how
In general if you have a series of if checks you could consider turning it into a switch instead. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/switch
Since you're mostly checking to make sure things are defined, but also have some more nuanced requirements, it may be better break it up slightly:
// Properties required irrespective of environment properties in DocumentItemNamesEnum or ReferenceCodesOfServicesEnum
const requiredProperties = ["customer",
"company", "aircraftType",
"aircraft", "endPlan",
"startPlan"]
const requiredPropertiesDefined = requiredProperties.every(value => !!value)
useEffect(() => {
if (requiredPropertiesDefined &&
(formValues.parking || formValues.service === ReferenceCodesOfServicesEnum.ProvisioningMinibus) &&
&& (formValues.heatingPointsMasterCodes ||
formValues.lavatoryType ||
formValues.passengersCategory ||
formValues[DocumentItemNamesEnum.WaterSystemMaintenance] ||
service === ReferenceCodesOfServicesEnum.AircraftCooling) {
setDisabled(true)
} else {
setDisabled(false
}
}, [formValues])
If you want to check if all values of object are truthy then following code can be used.
Object.values(formValues).every(value => !!value)

Custom function in function chain - To get Length of Array

I am trying to extract the length of an array while mapping it.
Here is what happens:
First I have an array of objects. Each object has a key of posts where I store the posts for that object. My code takes all the posts from all the objects and maps them to a new array so that I can show all the posts from all the objects to the user on the front end.
I'd like to show only 10 posts at a time. So I put a .slice(0, page * 10) - the variable page is controlled by a button at the bottom of the page. If the user hits the button, the page then increases the number of posts on the screen.
This all works great. BUT - I'd like to be able to count the total number of posts and only show the button when there are more posts available. Is there a way to extract the number of posts while still allowing it to map the results from this function below?
{
bandTypes === "all"
? allBands
.filter(band => {
if (showType !== 'Show Type') {
return band.showTypes.includes(showType)
} else {
return band
}
})
.reduce(
(allPosts, band) =>
allPosts.concat(
(band.youtube.length > 0 &&
band.bandBio !== "n/a" &&
band.bandGenre !== "n/a")
? band.posts.map((post) => ({ post, band }))
: []
),
[]
)
.sort((a, b) => new Date(b.post.date) - new Date(a.post.date))
.slice(0, page * 10)
.map(({ post, band }) => <div key={uuidv4()}>{convertPost(post, band)}</div>)
: null
}
It would be great if I could just put an anonymous function in there somewhere that sets the state to the length of the array.
I think that trying to accomplish this without any temporary variables is not going to be very efficient and probably would be quite ugly.
I think you should first create the array of all posts and then simply use it's length inside the return of your component.
Here's an example of how I would do it:
const MyComponent = () => {
let allPosts = []
if (bandTypes === "all") {
allPosts = allBands
.filter((band) => {
if (showType !== "Show Type") {
return band.showTypes.includes(showType)
} else {
return band
}
})
.reduce(
(allPosts, band) =>
allPosts.concat(
band.youtube.length > 0 &&
band.bandBio !== "n/a" &&
band.bandGenre !== "n/a"
? band.posts.map((post) => ({
post,
band,
}))
: []
),
[]
)
.sort((a, b) => new Date(b.post.date) - new Date(a.post.date))
}
return (
<div>
{allPosts.slice(0, page * 10).map(({ post, band }) => (
<div key={uuidv4()}>{convertPost(post, band)}</div>
))}
{allPosts.length < page * 10 && <button>Show more posts</button>}
</div>
)
}
(BTW using something like uuidv4 for the key isn't ideal, because React is less efficient with rendering then. It's better base the key on something that is unique to each post and doesn't change for each render, for example an id from the database or something of this sort)

How can I refactor a long if-else statement checking array length?

I am writing a program that uses checkboxes to filter a list of items.
I have a group of 3 checkboxes: phase, specialty, and type. Once the checkboxes are marked, they are put into an array which is then used to filter out a list if the conditions match.
When making this, I ran into a few issues:
If no boxes were checked, then no list would appear at all.
If a box in all categories were checked, then it would show both conditions rather than when both are true (so if I had phase Base 1 and specialty Race checked, then the list would show all Race matches and all Base 1 matches)
If a box was not checked but another one was, then nothing would appear since it didn't fit both categories.
To fix all three of these issues, I made an if, if/else statement to check the array length of each category. If all arrays (checkboxes) were empty (unchecked), then the original list would appear. If 1 box was checked, but the others weren't, then nothing would break. Etc.. This was made into quite a long statement.
Now, I do plan to add 2-3 more checkbox options, and don't want to complicate things even more. If I keep doing the way that I'm doing now, I may end up with double the current statements I have now.
Currently, the way this list is being filtered is the following, note, the console.logs are to recognize which condition I am using:
if (phases.length === 0 && specialties.length === 0 && type.length === 0) {
const workouts = this.workouts;
this.selectedWorkouts.next(workouts);
} else if (phases.length > 0 && specialties.length > 0 && type.length > 0) {
const workouts = this.workouts.filter(
workout => byPhase(workout) && bySpecialty(workout) && byType(workout)
);
this.selectedWorkouts.next(workouts);
console.log("1 EVERYTHING CHECKED");
} else if (
phases.length > 0 &&
specialties.length > 0 &&
type.length === 0
) {
const workouts = this.workouts.filter(
workout => byPhase(workout) && bySpecialty(workout)
);
this.selectedWorkouts.next(workouts);
console.log("2 PHASE AND SPECIALTY (no type)");
} else if (
phases.length > 0 &&
specialties.length === 0 &&
type.length > 0
) {
const workouts = this.workouts.filter(
workout => byPhase(workout) && byType(workout)
);
this.selectedWorkouts.next(workouts);
console.log("3 PHASE AND TYPE (no specialty)");
} else if (
phases.length > 0 &&
specialties.length === 0 &&
type.length === 0
) {
const workouts = this.workouts.filter(workout => byPhase(workout));
this.selectedWorkouts.next(workouts);
console.log("4 PHASE ONLY (no type or specialty)");
} else if (
phases.length === 0 &&
specialties.length > 0 &&
type.length > 0
) {
const workouts = this.workouts.filter(
workout => bySpecialty(workout) && byType(workout)
);
this.selectedWorkouts.next(workouts);
console.log("5 SPECIALTY AND TYPE (no phase)");
} else if (
phases.length === 0 &&
specialties.length > 0 &&
type.length === 0
) {
const workouts = this.workouts.filter(workout => bySpecialty(workout));
this.selectedWorkouts.next(workouts);
console.log("6 SPECIALTY ONLY (no phase nor type)");
} else if (
phases.length === 0 &&
specialties.length === 0 &&
type.length > 0
) {
const workouts = this.workouts.filter(workout => byType(workout));
this.selectedWorkouts.next(workouts);
console.log("7 TYPE ONLY (no phase nor specialty)");
}
Is there a way to refactor this so I don't continue to add on to the statements, making it an even longer block of code? Or is this not really much of a concern to keep my statement so long?
Here is the stackblitz to my full project. You can find the if/else statements in src/app/features/workouts-page/workoutservice/workout.service.ts. The code is found above should be specific enough for this statement.
Thank you.
Here's how I would do it. This uses a single filter pass:
filterWorkouts(phases: string[], specialties: string[], types: string[]) {
const workouts = this.workouts.filter(workout => {
return (
(phases.length === 0 || phases.indexOf(workout.phase) >= 0) &&
(specialties.length === 0 || specialties.indexOf(workout.specialty) >= 0) &&
(types.length === 0 || types.indexOf(workout.type) >= 0)
);
});
this.selectedWorkouts.next(workouts);
}
You would need to add a one-liner for each additional filter is all. Here's a working implementation on stackblitz for you to play around with.
Since the byPhase and related functions are just functions, you could store them in an array based on the values. Then you could call the functions within the array to pass up to your filter.
function byPhase(workout) { console.log('by phase'); }
function bySpecialty(workout) { console.log('by specialty'); }
function byType(workout) { console.log('by type'); }
// This should filter without specialties
phases = [1,2,3];
specialties = [];
type = [3,4];
const workoutFilters = [
phases.length > 0 ? byPhase : null,
specialties.length > 0 ? bySpecialty : null,
type.length > 0 ? byType: null,
].filter(Boolean);
// Show selected filters
console.log('filters:', workoutFilters);
You can always return from your if to avoid using an else if as long as there isn't something afterwards. In your case, there wasn't so that's one way to shorten things up.
Also, len === 0 can always be replaced with !len as it's a bit more succinct and instead of len > 0 it can just be if (len) if you're comfortable with that, some don't find it as read-able so I'll leave it up to you.
I noticed the same line came after filter op: this.selectedWorkouts.next(workouts); so I moved that to the end and only wrote it once outside of the other blocks to avoid having it run at the end of each block.
Then I noticed that you are basically just trying to use that filter if one of the filters is applied so instead of else if, I used 3 if statements that will apply the filtered param and moved workouts up in scope and gave it its own unique variable so instead of filtering on this.workouts you are filtering on the wo in the fn and updating it each time. This way, you don't have to check for the negative conditions as it won't filter them if the condition doesn't apply and will filter again if a second param applies (gives you the && without writing out every condition).
I think this is a very read-able solution that mostly preserves the code you've written so far.
filterWorkouts(phases: string[], specialties: string[], type: string[]) {
const byPhase = workout => phases.some(phase => workout.phase === phase);
const bySpecialty = workout =>
specialties.some(specialty => workout.specialty === specialty);
const byType = workout => type.some(type => workout.type === type);
let wo = this.workouts;
if (phases.length) {
wo = wo.filter(workout => byPhase(workout));
console.log("CHECKED PHASE");
}
if (specialties.length) {
wo = wo.filter(workout => bySpecialty(workout));
console.log("CHECKED SPECIALTY");
}
if (type.length) {
wo = wo.filter(workout => byType(workout));
console.log("CHECKED TYPE");
}
this.selectedWorkouts.next(wo);
return;
}
}

What is the correct approach to check if array exists in JS?

I'm getting the below error when I check the length of an array. What would be the correct approach?
main.js
if (drugPrice.mailPrice.rejectMessage.length !== 0 && Array.isArray(drugPrice.mailPrice.rejectMessage)) {
//code goes here
}
Error
TypeError: Cannot read property 'length' of undefined
Try swapping the order of the checks:
if (Array.isArray(drugPrice.mailPrice.rejectMessage) && drugPrice.mailPrice.rejectMessage.length !== 0) {
code goes here
}
Validate your data, swapping the condition may help but it won't prevent, some errors from happeing. For example Array.isArray(drugPrice.mailPrice.rejectMessage) will throw an error if drugPrice.mailPrice is undefined.
if (drugPrice.mailPrice
&& drugPrice.mailPrice.rejectMessage
&& drugPrice.mailPrice.rejectMessage.length !== 0
&& Array.isArray(drugPrice.mailPrice.rejectMessage)) {
// code goes here
}
var drugPrice = { mailPrice: { rejectMessage: {} } };
if (drugPrice.mailPrice
&& drugPrice.mailPrice.rejectMessage
&& drugPrice.mailPrice.rejectMessage.length !== 0
&& Array.isArray(drugPrice.mailPrice.rejectMessage)) {
console.log('success');
} else {
console.log('fail')
}
NOTE
Always validate your data. Don't assume that you'll always get the right data. When working with objects always validate them, as doing data.name, can break your app, if data is null or undefined. for example, given the following object.
const drugPrice = { mailPrice: null };
doing, throws an error.
const drugPrice = { mailPrice: null };
// throws an error, Cannot read property 'rejectMessage' of undefined
if (Array.isArray(drugPrice.mailPrice.rejectMessage)) {
}
to prevent that from happening, we need to check if the propery exists, like the following.
const drugPrice = { mailPrice: null };
console.log(drugPrice.mailPrice && Array.isArray(drugPrice.mailPrice.rejectMessage) || 'Price is null or undefined')
You do not really need to actually do the .length !== 0. You can simply do:
if (Array.isArray(A.B.C) && A.B.C.length) { // <-- order is important here
//...
}
.length would be evaluated as a boolean and it will give you the same result as checking with !==0
That being said however your paths are quite long so you probably would want to make sure they are valid. Meaning if drugPrice or mailPrice are falsey you would have an issue. So usually you would want to check on them as well. Since your question was about the array part I will skip those but just as FYI.
You can build your own path checker or if you use libraries like lodash/underscore etc they always have a handy get/has functions to check like this (with lodash):
if (_.has(drugPrice, 'mailPrice.rejectMessage.length'))
//...
}
Obviously do not use those libraries just for that but if you already have them those methods are quite handy. You can simply check each of the paths as well via:
if (A && A.B && Array.isArray(A.B.C) && A.B.C.length) {
//...
}
It just gets tedious if you have long object paths etc.
The problem in your code is that javascript checks array length before checking if the array is the type of the array. You should change the order in the if statement.
You can try with:
if (myArr && Array.isArray(myArr) && myArr.length !== 0) {
// your code
}
Now the code is executed in the right order.
The first condition checks if myArr is defined,
The second condition checks if myArr is the type of Array, you can also do this way:
if (myArr && myArr.push && myArr.length !== 0) {
// your code
}
The third condition checks if myArr is not empty.

Multiple filters using lodash in React

I have a function that is filtering a large set of objects based on inputs that looks like so:
filterLocations(filters) {
let filteredLocations = _.filter(
this.state.locations,
location =>
location.beds >= filters.min &&
location.beds <= filters.max &&
location.baths >= filters.bathrooms &&
_.indexOf(filters.buildingTypes, location.buildingType.name) !== -1
);
this.setState({ filteredLocations: filteredLocations });
}
and in another component this is where the filters are set:
let filters = {
min: this.state.min || 0,
max: this.state.max || 99,
bathrooms: this.state.bathrooms || 0,
buildingTypes: this.state.selectedTypes || []
};
The first three work fine because the 'default values' are set regardless, so it makes filtering easy. But I am having trouble with figuring out the last part. If I select a buildingType it's fine and the filtering works as expected, but obviously if I leave it blank, the _.index(...) part tries to sort on nothing, so the resulting array is empty. I was wondering what would be the best way to rework the _.indexOf(...) part so I dont have to do something like:
buildingTypes: this.state.selectedTypes || ['list all options here']
Could you use a ternary to conditionally include the indexOf, and default to true without any buildingTypes so the expression will evaluate to true if all of the others are also true?
filterLocations(filters) {
const filteredLocations = _.filter(
this.state.locations,
location =>
location.beds >= filters.min &&
location.beds <= filters.max &&
location.baths >= filters.bathrooms &&
(filters.buildingTypes.length > 0
? _.indexOf(filters.buildingTypes, location.buildingType.name) !== -1
: true)
);
this.setState({ filteredLocations });
}

Categories

Resources