Multiple filters using lodash in React - javascript

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

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)

Javascript Return Conditionals

searchList(array, filters) {
var currentUserId = 125;
var list = array;
// array example [{aisle: A9, userid: 125},{aisle: B2, userid: null},{aisle: C#, userid: 125}]
// filters example {assignedButtonActive: true, unassignedButtonActive: false, aisle: A9}
result = Object.keys(list)
.map((key) => {
return { ...list[key] };
})
.filter((data) => {
// If assigned or unassigned buttons are not active, this filter doesnt apply and aisle should be the only thing filtering
let assigned = filters.assignedButtonActive
let unassigned = filters.unassignedButtonActive
let aisleSelection = filter.aisle;
let aisle = data.aisle;
let userid = data.userid;
return aisle.indexOf(aisleSelection) > -1 // Need a conditional to also filter out assigned/unassigned if the buttons are active, otherwise not needed.
});
return result;
}
I am trying to filter a list with the combination of an input search and button/flags. I have no problem with the input search function filtering and returning the list. The problem Im having is using boolean flags along with the search input to be even more exact on what i want to filter, but I am having an issue on how to return the result using conditionals/booleans in this case. I cant seem to mix the two conditions for a return with both filters applied. I've tried something like this return aisle.indexOf(aisleSelection) > -1 && (assigned) ? assignedTo == currentUserId : (unassigned) ? assignedTo == null : [] but seems I'm way off. For example if either assigned/unassigned flags are active, it will add one of these two filters assignedTo === currentUserId or assignedTo == null to filter along with aisle filter as well, pretty much so they can work together as well, not one or the other
Not looking for the solution written out for me, more on how I can handle this filtering beyond just an input, with the possibility to off more filters being used
Any help/tips would be greatly appreciated
Finish out the function before returning, then have different return statements for each condition
rather than
return aisle.indexOf(aisleSelection) > -1
try
if (aisle.indexOf(aisleSelection) > -1) {
return true}
else if (other possibility){
return true}
else (case to filter out){
return false}
filter gets called on each element in the array, and decides to keep the element if the callback function returns true
Based on what I have understood, follow through this code especially the part I have commented then let me know if this what you wanted. If there's you don't get, let me know.
function searchList(array, filters) {
var currentUserId = 125;
var list = array;
result = Object.keys(list)
.map((key) => ({ ...list[key] }))
.filter((data) => {
let assignedSelection = filters.assignedButtonActive;
let unassignedSelection = filters.unassignedButtonActive;
let aisleSelection = filters.aisle;
let aisle = data.aisle;
let userid = data.userid;
return Boolean(Boolean(assignedSelection) || Boolean(unassignedSelection)) && aisle.includes(aisleSelection)?assignedTo = currentUserId:assignedTo = null;
/**
Explanation:
I am not sure of what you wanted to achieve with this, but follow through and see If this is what you wanted.
1. Boolean(Boolean(assignedSelection) || Boolean(unassignedSelection))
This part will return true if either assignedSelection or unassignedSelection is true.
This part will return false if both assignedSelection and unassignedSelection are false.
2. aisle.includes(aisleSelection)
This part will return true if aisleSelection matches aisle
This part will return false if aisleSelection does not match aisle
3. Boolean(Boolean(assignedSelection) || Boolean(unassignedSelection)) && aisle.includes(aisleSelection)
This part will return true if both part_(1) and part_(2) return true
Thus part will return false if either part_(1) or part_(2) returns false
4. return Boolean(Boolean(assignedSelection) || Boolean(unassignedSelection)) && aisle.includes(aisleSelection)? assignedTo = currentUserId : assignedTo = null;
This part will return null if part_(3) returns false
This part will give the variable assignedTo the value currentUserId if part_(3) returns true
*/
});
return result;
}
//Execute the example
var arrayExample = [
{ aisle: "A9", userid: 125 },
{ aisle: "B2", userid: null },
{ aisle: "C#", userid: 126 },
];
var filtersExample = {
assignedButtonActive: true,
unassignedButtonActive: false,
aisle: "A9",
};
searchList(arrayExample, filtersExample);
in such cases, I use the usual brackets to show the individual parts of the inequality. example
return aisle.indexOf(aisleSelection) > -1 && (assigned) ? assignedTo == currentUserId : ((unassigned) ? assignedTo == null : [])
but maybe
return assignedTo = (aisle.indexOf(aisleSelection) > -1 && (assigned) ? currentUserId : ((unassigned) ? null : []))
I may have misunderstood the necessary conditions. if you describe the conditions in more detail, the answer will be more correct.

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

Node js filter array with reactive form values

I've got probelm with filtering array from form values.
I using angular 7 and express js
export function getItems(req: Request, res: Response) {
const itemCriteria: ItemCriteria = req.body.itemCriteria;
const name = (itemCriteria.name) ? itemCriteria.name : '';
const description= (itemCriteria.description) ?
itemCriteria.description: '';
const category= itemCriteria.category;
const filterItems: Item[] = items
.filter(item=> item.category.id === category &&
item.name .includes(name ) &&
item.description.includes(description)
);
res.status(200).json(filterItems);
}
When I fill all fields filterItems is correct, but when I fill only one or two fields I've got error 'Cannot read property of undefinied'. This fields is from reactive form but only one is required else is optional. So my question is how can I filter this array correct?
When one of Criteria is empty, you don't have to include it in the filter function.
So you can change it by following.
const filterItems: Item[] = items
.filter(item=> (!category || (item.category && item.category.id === category)) &&
(!name || (item.name && item.name.includes(name))) &&
(!description || (item.description && item.description.includes(description))
);
You are trying to read a property of an undefined variable. It's hard to know which one it is, but you can protect your code from these types of errors by making sure a variable is defined like this:
const myProperty = someVariable && someVariable.someProperty
This shortcircuits if someVariable is falsy.
In your case this might look like:
const filterItems: Item[] = items
.filter(item=> item && item.category && item.category.id === category &&
item.name && item.name.includes(firstName) &&
item.description && item.description.includes(lastName)
);
(also it's not clear where firstName, lastName, and items come from, but I don't think that's the cause of the error you're asking about)

Based on array elements returning different output

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.

Categories

Resources