JavaScript - Explanation of reduce example - javascript

Its my first question on StackOverflow (if i do something wrong - sorry!)
I have a reducer function where my teacher helped me, and i can really grasp what it means.
It's a todo app where i have a button. When pressing the button all items in the todo list should be marked as completed. When i press the button again, all items should be in-completed.
I get most of the code. The one part I cant graps my mind round is the explanation mark before the items array !store.items.find.
If anyone could give an explanation i would be thankful!
Also let me know if you need me to share more code.
completeAllTodo: (store, action) => {
const areAllTasksCompleted = !store.items.find(todo => !todo.isComplete)
if (areAllTasksCompleted) {
const completedItems = store.items.map(todo => {
return {
...todo,
isComplete: false
}
})
store.items = completedItems
} else {
const completedItems = store.items.map(todo => {
return {
...todo,
isComplete: true
}
})
store.items = completedItems
}
},

well
store.items.find(todo => !todo.isComplete)
means find any item who is not complete yet if there is no such an item then find will return undefined, and so
!store.items.find(todo => !todo.isComplete)
means if there is an item which is not completed then this will be false if all items are complete then find will return undefined and this whole expression will be true
a better way to express the same logic will be
const areAllTasksCompleted = store.items.every(todo => todo.isComplete)

const areAllTasksCompleted = !store.items.find(todo => !todo.isComplete)
store.items.find returns the first item that matches criteria of the predicate callback.
todo => !todo.isComplete is your predicate callback, which is passed one argument (the current item in the iteration) and returns true if the item is not complete (or false if it is complete).
Therefore, this expression will return the value of the first element in the array that is not completed.
The ! before the whole expression negates the result of the expression. In JavaScript, any object value will be coerced to true, and a null or undefined value will be coerced to false, so if the expression finds a value that matches the predicate, your const will be assigned to false, otherwise it will be true.

Array#find will return an array of matching items if any otherwise returns undefined.
So, if there's some incomplete todos find will return an array and negating that array will result in false, and if there's no incomplete todos it will return undefined and negating undefined will result in true.
Consider the code snippets below:
const todo = [
{ task: "Exercise", isComplete: true },
{ task: "Buy Grocery", isComplete: false },
];
// There is one incomplete todo in the above todo array
const resultFromFind = todo.find(t => !t.isComplete);
console.log(resultFromFind); // { task: 'Buy Grocery', isComplete: false }
const allComplete = !resultFromFind
console.log(allComplete) // false
const todo = [
{ task: "Exercise", isComplete: true },
{ task: "Buy Grocery", isComplete: true },
];
// There is no incomplete todo in the above todo array
const resultFromFind = todo.find(t => !t.isComplete);
console.log(resultFromFind); // undefined
const allComplete = !resultFromFind
console.log(allComplete) // true

Related

Process multiple request queries

I have an API that serves JSON data. Currently if you do api/weapons for example it gives you all the weapons available, api/weapons/weaponName gives information about that specific weapon. What I want to do is be able to api/weapons?type=sword&rarity=5 for example. I managed to pull of api/weapons?type=sword and api/weapons?rarity=5 on their own but not together.
Here's what I'm currently doing:
let filtered = [];
if (query.type) {
filtered = filtered.concat((await weapons).filter(w => formatName(w.weaponType) === formatName(query.type)));
}
if (query.rarity) {
filtered = filtered.concat((await weapons).filter(w => w.rarity == query.rarity));
}
if (!filtered.length) filtered = [await weapons]
res.status(HttpStatusCodes.ACCEPTED).send(filtered);
formatName is just a function that makes the string all lowercase and trims it and removes all spaces.
If we take api/weapons?type=sword&rarity=5
I think what's happening right now is:
It is getting all the weapons with the type "sword"
It is getting all the weapons with the rarity "5"
It is joining all the results together, so all the weapons with the type sword (regardless of rarity) and al the weapons with the rarity 5 (regardless of type).
I want it to filter weapons with ONLY that rarity AND ONLY that type. So only 5 rarity swords for example. What is the most beneficial way of handling this
I'd suggest retrieving "weapons" once and then running any filters on them without concatenating the results:
let filtered = [ ...(await weapons) ];
if (query.type) {
filtered = filtered.filter(w => w => formatName(w.weaponType) === formatName(query.type));
}
if (query.rarity) {
filtered = filtered.filter(w => w.rarity == query.rarity);
}
res.status(HttpStatusCodes.ACCEPTED).send(filtered);
Your current logic is testing whether one constraint OR another matches, what you actually need to do is to do an AND, which means you must perform the test in a single pass of filter.
I would slightly modify your code so that you compare all constraints that you're sending...you could further modify the logic below to accept a logical operator to test whether the rarity is >= or <= to a certain number for example.
const weapons = [{
type: 'sword',
name: 'swift blade of zek',
rarity: 5
},
{
type: 'mace',
name: 'hammer of kromzek kings',
rarity: 1
},
{
type: 'sword',
name: 'split blade of thunder',
rarity: 2
},
{
type: 'sword',
name: 'blade of carnage',
rarity: 5
},
]
const getWeapons = (query = {}) => {
let filtered = [];
let constraints = [];
// We could build this object dynamically but I just wanted
// to demonstrate it using your current approach
if (query.hasOwnProperty('type')) {
constraints.push({
name: 'type',
value: query.type
})
}
if (query.hasOwnProperty('rarity')) {
constraints.push({
name: 'rarity',
value: query.rarity
})
}
// Compare all of the conditions and only return weapons
// that match all of the conditions passed.
filtered = weapons.filter(w => {
let matches = 0
constraints.forEach(c => {
if (w[c.name] === c.value) {
matches += 1
}
})
// ensures we only return complete matches
return matches === constraints.length
});
return filtered
}
console.log(getWeapons({
type: 'sword',
rarity: 5
}))
Create an object which has the same property keys as the filters you want to use. Assign a function to each property where the evaluation for that specific filter is specified.
const filters = {
type: (weapon, type) => formatName(weapon.weaponType) === formatName(type),
rarity: (weapon, rarity) => weapon.rarity === rarity,
};
Then loop over the weapons with filter. Inside the filter loop, loop over the keys of the query variable with the every method. This method will return true or false based on if every evaluation is true or not.
In the every loop, use the keys of the query to select the filter from the filters list. Pass the weapon and the values of the query object to these filter functions and return result.
By doing this you can use one, two or no filters at all. And any new filters can be added in the filters object.
const filteredWeapons = weapons.filter((weapon) =>
Object.keys(query).every((filterKey) => {
if (!(filterKey in filters)) {
return false;
}
const filter = filters[filterKey]
const value = query[filterKey]
return filter(weapon, value);
})
);
res.status(HttpStatusCodes.ACCEPTED).send(filteredWeapons);

How can I filter through an array of objects based on a key in a nested array of objects?

I am having a hard time filtering through an array of objects based on a value in a nested array of objects. I have a chat application where a component renders a list of chats that a user has. I want to be able to filter through the chats by name when a user types into an input element.
Here is an example of the array or initial state :
const chats= [
{
id: "1",
isGroupChat: true,
users: [
{
id: "123",
name: "Billy Bob",
verified: false
},
{
id: "456",
name: "Superman",
verified: true
}
]
},
{
id: "2",
isGroupChat: true,
users: [
{
id: "193",
name: "Johhny Dang",
verified: false
},
{
id: "496",
name: "Batman",
verified: true
}
]
}
];
I want to be able to search by the Users names, and if the name exists in one of the objects (chats) have the whole object returned.
Here is what I have tried with no results
const handleSearch = (e) => {
const filtered = chats.map((chat) =>
chat.users.filter((user) => user.name.includes(e.target.value))
);
console.log(filtered);
// prints an empty array on every key press
};
const handleSearch = (e) => {
const filtered = chats.filter((chat) =>
chat.users.filter((user) => user.name.includes(e.target.value))
);
console.log(filtered);
// prints both objects (chats) on every keypress
};
Expected Results
If the input value is "bat" I would expect the chat with Id of 2 to be returned
[{
id: "2",
isGroupChat: true,
users: [
{
id: "193",
name: "Johhny Dang",
verified: false
},
{
id: "496",
name: "Batman",
verified: true
}
]
}]
The second approach seems a little closer to what you're trying to accomplish. There's two problems you may still need to tackle:
Is the search within the name case insensitive? If not, you're not handling that.
The function being used by a filter call needs to return a boolean value. Your outer filter is returning all results due to the inner filter returning the array itself and not a boolean expression. Javascript is converting it to a "truthy" result.
The following code should correct both of those issues:
const filtered = chats.filter((chat) => {
const searchValue = e.target.value.toLowerCase();
return chat.users.filter((user) => user.name.toLowerCase().includes(searchValue)).length > 0;
});
The toLowerCase() calls can be removed if you want case sensitivity. The .length > 0 verifies that the inner filter found at least one user with the substring and therefore returns the entire chat objects in the outer filter call.
If you want to get object id 2 when entering bat you should transform to lowercase
const handleSearch = (e) =>
chats.filter(chat =>
chat.users.filter(user => user.name.toLowerCase().includes(e.target.value)).length
);
try this it should work
const handleSearch2 = (e) => {
const filtered = chats.filter((chat) =>
chat.users.some((user) => user.name.includes(e))
);
console.log(filtered);
};
filter needs a predicate as argument, or, in other words, a function that returns a boolean; here some returns a boolean.
Using map as first iteration is wrong because map creates an array with the same number of elements of the array that's been applied to.
Going the easy route, you can do this.
It will loop first over all the chats and then in every chat it will check to see if the one of the users' username contains the username passed to the function. If so, the chat will be added to the filtered list.
Note, I am using toLowerCase() in order to make the search non case sensitive, you can remove it to make it case sensitive.
const handleSearch = (username) => {
var filtered = [];
chats.forEach((chat) => {
chat.users.forEach((user) => {
if (user.name.toLowerCase().includes(username.toLowerCase())) {
filtered.push(chat);
}
});
});
console.log(filtered);
return filtered;
}
handleSearch('bat');

How to search nested object by following JSLint

I have my object structured as below and I want to find the product with provided ID.
0 :{
id: 0,
title: 'xxxx',
url: "www.test.com"
quantity: 100
},
1 :{
id: 10,
title: 'xxxx',
url: "www.test.com"
quantity: 100
},
// and so on...
In order to search nested attribute within the object, I have written the below function:
export const selectProductById = (state, productId) => {
const obj_index = Object.keys(state.products).find(function(idx) {
if (state.products[idx].id == productId) {
return idx;
}
}
return state.products[obj_index]
}
This works but I will always get a warning during compilation of my react app.
Expected '===' and instead saw '=='
But if I change this into === the code will not work anymore, does anyone knows how to change this so that it follows JSLint rules ?
It sounds like the productId is not a number. Cast it to a number first:
if (state.products[idx].id === Number(productId)) {
But you should return a truthy or falsey value from the .find callback, not something that you're iterating over (since you may not be sure whether it's truthy or falsey, and it's potentially confusing). Return the result of the === comparison instead:
const { products } = state;
const obj_index = Object.keys(products).find(
key => products[key].id === Number(productId)
);

is there a way to filter array based on flag inside an array

I am trying to do filter an array based on flag isSome: true. I want to filter when that flag is not present in that array
var value = [
{ "somevalues": {},
"moreDetails": {
"isSome": "true"
}
},
{ "somevalues": {},
"moreDetails": {}
},
{ "somevalues": {},
"moreDetails": {}
},
]
const valuewithisSome = value.filter(o => o.moreDetails && o.moreDetails.isSome);
const valuewithoutisSome = value.filter(o => o.moreDetails && !o.moreDetails.isSome);
console.log(valuewithisSome);
console.log(valuewithoutisSome);
valuewithisSome is working as expected and returning array with isSome: true.
valuewithoutisSome is not working as expected as i don't want to pass isSome false in morevalues array, is there a way to filter without even passing that flag?
The "without" code you already have should work:
const valuewithoutisSome = value.filter(o => o.moreDetails && !o.moreDetails.isSome);
The expression o.moreDetails.isSome will evaluate to undefined if the "isSome" property is missing or explicitly false-y, so !o.moreDetails.isSome will be true in that case.
Now while that will work, if you want to explicitly test for the property being false only when it's actually present, you can do this:
const valuewithoutisSome = value.filter(o =>
o.moreDetails &&
(!("isSome" in o.moreDetails) || !o.moreDetails.isSome)
);
That will only return false (and filter out the array entry) when the "isSome" property is present. If it's not, or if it's present and true, then the entry will be in the filtered result.
Your isSome property type is string. Remove double quetos from true.
{ "somevalues": {},
"moreDetails": {
"isSome": true
}
},

Typescript - if conditrional inside a map

I am mapping a subset of user data to an object of a refined data set. Inside the map i want to check if a variable is null or undefined, and if yes, then to set this variable to a placeholder value.
The issue I am facing is that declaring an if statement inside the map is causing an error, but even though a map can have an index as a parameter, how can we use it functionally with a conditional Statement? Any insight most appreciated.
return this.UserService.search(url)
.map((data) => {
console.log(data);
data.result = <any> data.result.map((user, index) => ({
// if statement causing error here
if(user.name === null || user.name === undefined){
// error with this if condition
},
id: user.id,
name: user.name,
type: user.type,
password: user.password,
}));
return data;
}).map(data => ({
meta: { totalItems: data.size },
data: data.result,
}));
You're attempting to use an object literal as the return type, but naturally, an if statement (or any statement) can't be inside object literal syntax.
So instead, define a function body, which also uses curly braces, and put your code inside with an explicit return statement.
// Starts function body instead of single expression-v
data.result = <any> data.result.map((user, index) => {
if (some_condition) {
return "some value"; // The object?
} else {
return "other value"; // A different object?
}
/*
// I assume these are to be used in the object you return
id: user.id,
name: user.name,
type: user.type,
password: user.password,
*/
});
You can express conditions in literal maps, but it is somewhat ugly.
return {
a: 1,
...(some_condition && {
b: 1,
})
};
As far as i know you can't do that with JUST a map.
however you could follow it up with a filter() function:
const newArray = oldArray.map((value, index) => condition ? value : null).filter(v => v);
you basicaly iterate over each item and then return the value or null depending on your condition.
Now once you have the map you just filter it by removing the null values from the array.
Notice that the original array is not altered and a new one is returned.
thanks for the idea #user8897421 for the idea. i just wanted to turn it into a one liner.

Categories

Resources