My code is as below:
if(existingWishlistItem) {
return wishlistItems.map(wishlistItem =>
wishlistItem.id === wishlistItemToAdd.id
? toast.error('This item is already in your wishlist')
: wishlistItem
)
}
I want this function to check if there are existing wishlist item in the array, then it pop up an error message to user and return back the wishlistItem array. But I find that I just can write one action after the '?', so are there any ways to pop up the message and return back the wishlistItem at the same time?
Thanks for help!
It's possible to do this with the conditional operator, but it's not a good idea. It's hard to read, hard to debug, and easy to get wrong.
Instead, just use an if:
if (existingWishlistItem) {
for (const {id} of wishlistItems) {
if (id === wishlistItemToAdd.id) {
toast.error('This item is already in your wishlist');
break; // I assume the ID values are unique, so you can stop here
// Or: `return wishlistItems;` if you don't need to make a
// copy in this case
}
}
return wishlistItems; // If you don't need to make a copy
// Or: `return wishlistItems.slice()` if you do need to make a copy
}
(Or — again assuming id values are unique — you could use find instead of the for-of loop to find the existing item.)
For completeness, you can use the comma operator to do two things in any expression (including the operands of the conditional operator): (first, second). The comma operator evaluates its left-hand operand, throws away that result, and then evalutes its right-hand operand and takes that value as its result. Applying that to your example:
// DON'T DO THIS
if (existingWishlistItem) {
return wishlistItems.map(wishlistItem =>
wishlistItem.id === wishlistItemToAdd.id
? (toast.error('This item is already in your wishlist'), wishlistItem)
: wishlistItem
);
}
This is not what map or ternaries are for.
Idiomatically, ternaries are used for conditional behavior that does not have side effects. Like return upperCase ? "HELLO" : "hello". This is because complex ternaries are hard to read and so it's hard to tell, at a glance, where the side effect is happening.
Likewise map is for transforming objects in a sequence according to some function. It's best practice for map to have no side effects, because code is easier to read when side-effects are clearly separated from data transformation.
A far more idiomatic implementation of your code would be:
if(existingWishListItem) {
if (wishlistItems.some(x => x.id === wishlistItemToAdd.id) {
toast.error(msg)
}
return wishListItems
}
Your problem is you're using map which populates a new list of data with the same array length. If you want to find an existing item, you just simply use find. For example
if(existingWishlistItem) {
const foundWishlistItem = wishlistItems.find(wishlistItem => wishlistItem.id === wishlistItemToAdd.id)
if(foundWishlistItem) {
toast.error('This item is already in your wishlist')
//TODO: You can return or do whatever after found existing wishlist item
}
return wishlistItems
}
Besides that, if you want to have true/false value instead of finding an existing object, you can use some instead
if(existingWishlistItem) {
const isFoundWishlistItem = wishlistItems.some(wishlistItem => wishlistItem.id === wishlistItemToAdd.id)
if(isFoundWishlistItem) {
toast.error('This item is already in your wishlist')
//TODO: You can return or do whatever after found existing wishlist item
}
return wishlistItems
}
Depends on how you define your action. You can use the || operator, and that way first expression is your alert and second is the value you return:
let x = [1,2,3];
let y = x.map((a) => a%2===0? (alert("XXx") || a) : a+1
);
console.log(y);
Related
I have an object array, and I want to test if one of them has a data-attr so I could make a simple if statement, here is an example of what I'm trying to do
if (array.hasAttribute("data-attr")) {}
I'm tried some() and every()
if (array.some(this.hasAttribute("data-attr"))) {}
but I can't figured out the right syntax, please help
array.some(this.hasAttribute("data-attr"))
Was almost correct. Array#some takes a predicate, so you need to do
array.some(function(el) {
return el.hasAttribute("data-attr");
})
Or shorter using arrow functions:
array.some(el => el.hasAttribute("data-attr"))
Array#every works the same way and takes a predicate, so you can call it the same way:
array.every(function(el) {
return el.hasAttribute("data-attr");
})
//or
array.every(el => el.hasAttribute("data-attr"))
And if you want to use one or the other, you can extract the predicate and re-use it:
var hasDataAttribute = el => el.hasAttribute("data-attr")
array.some(hasDataAttribute)
array.every(hasDataAttribute)
If you have jQuery objects, you have to use some tricks since jQuery doesn't have a method that returns true or false for the existence attributes. You can easily simulate it with obj.attr('some-attribute') !== undefined - this will return true if some-attribute exists and false if it doesn't.
var hasDataAttribute = jQueryEl => jQueryEl.attr('data-attr') !== undefined
For data-* attriutes, you can also use jQuery.data() which is (in this case) a shorthand element.attr('data-something') is the same as element.data('something'):
var hasDataAttribute = jQueryEl => jQueryEl.data('attr') !== undefined
So I'm in a unique situation where I have two objects, and I need to compare the keys on said objects to make sure they match the default object. Here's an example of what I'm trying to do:
const _ = require('lodash');
class DefaultObject {
constructor(id) {
this.id = id;
this.myobj1 = {
setting1: true,
setting2: false,
setting3: 'mydynamicstring'
};
this.myobj2 = {
perm1: 'ALL',
perm2: 'LIMITED',
perm3: 'LIMITED',
perm4: 'ADMIN'
};
}
}
async verifyDataIntegrity(id, data) {
const defaultData = _.merge(new DefaultObject(id));
if (defaultData.hasOwnProperty('myoldsetting')) delete defaultData.myoldsetting;
if (!_.isEqual(data, defaultData)) {
await myMongoDBCollection.replaceOne({ id }, defaultData);
return defaultData;
} else {
return data;
}
}
async requestData(id) {
const data = await myMongoDBCollection.findOne({ id });
if (!data) data = await this.makeNewData(id);
else data = await this.verifyDataIntegrity(id, data);
return data;
}
Let me explain. First, I have a default object which is created every time a user first uses the service. Then, that object is modified to their customized settings. For example, they could change 'setting1' to be false while changing 'perm2' to be 'ALL'.
Now, an older version of my default object used to have a property called 'myoldsetting'. I don't want newer products to have this setting, so every time a user requests their data I check if their object has the setting 'myoldsetting', and if it does, delete it. Then, to prevent needless updates (because this is called every time a user wants their data), I check if it is equal with the new default object.
But this doesn't work, because if the user has changed a setting, it will always return false and force a database update, even though none of the keys have changed. To fix this, I need a method of comparing the keys on an object, rather any the keys and data.
That way, if I add a new option to DefaultObject, say, 'perm5' set to 'ADMIN', then it will update the user's object. But, if their object has the same keys (it's up to date), then continue along your day.
I need this comparison to be deep, just in case I add a new property in, for example, myobj1. If I only compare the main level keys (id, myobj1, myobj2), it won't know if I added a new key into myobj1 or myobj2.
I apologize if this doesn't make sense, it's a very specific situation. Thanks in advance if you're able to help.
~~~~EDIT~~~~
Alright, so I've actually come up with a function that does exactly what I need. The issue is, I'd like to minify it so that it's not so big. Also, I can't seem to find a way to check if an item is a object even when it's null. This answer wasn't very helpful.
Here's my working function.
function getKeysDeep(arr, obj) {
Object.keys(obj).forEach(key => {
if (typeof obj[key] === 'object') {
arr = getKeysDeep(arr, obj[key]);
}
});
arr = arr.concat(Object.keys(obj));
return arr;
}
Usage
getKeysDeep([], myobj);
Is it possible to use it without having to put an empty array in too?
So, if I understand you correctly you would like to compare the keys of two objects, correct?
If that is the case you could try something like this:
function hasSameKeys(a, b) {
const aKeys = Object.keys(a);
const bKeys = Object.keys(b);
return aKeys.length === bKeys.length && !(aKeys.some(key => bKeys.indexOf(key) < 0));
}
Object.keys(x) will give you all the keys of the objects own properties.
indexOf will return a -1 if the value is not in the array that indexOf is being called on.
some will return as soon as the any element of the array (aKeys) evaluates to true in the callback. In this case: If any of the keys is not included in the other array (indexOf(key) < 0)
Alright, so I've actually come up with a function that does exactly what I need. The issue is, I'd like to minify it so that it's not so big. Also, I can't seem to find a way to check if an item is a object even when it's null.
In the end, this works for me. If anyone can improve it that'd be awesome.
function getKeysDeep(obj, arr = []) {
Object.keys(obj).forEach(key => {
if (typeof obj[key] === 'object' && !Array.isArray(obj[key]) && obj[key] !== null) {
arr = this.getKeysDeep(obj[key], arr);
}
});
return arr.concat(Object.keys(obj));
}
getKeysDeep(myobj);
Many times I ask myself the same question... With all that syntaxes (not always intuitive) to write quite direct code in JS, I was wondering, would someone know about a one-liner for that kind of operation?
var setFeatured = entry => {
entry.isFeatured = true;
return entry
}
SomeCallThatReturnsAPromise.then(entries => entries.map(setFeatured))
To assign a property and return the object in one shot, that I could put in a readable way directly as arg of entries.map
To give a feedback about what was proposed to me, the common answer was to return a result with a OR operator, after an assignation or function call (which returns undefined, null, false, never, well anything that will trigger the part after the OR):
return entry.isFeatured = true || entry
The interest of my question was to know if I could take advantage of a more compact syntax:
SomeCallThatReturnsAPromise()
.then((entries:EntryType[]) => entries
.map(entry => entry.isFeatured = true || entry)
.filter(entry => entry.something == true))
.then((entries:EntryType[]) => someCallThatReturnsNothingButHasToBeDoneThere() || entries)
.then((entries:EntryType[]) => console.log(entries))
would be easier to read than:
SomeCallThatReturnsAPromise
.then((entries:EntryType[]) => entries
.map(entry => {
entry.isFeatured = true;
return entry;
})
.filter(entry => entry.something == true))
.then((entries:EntryType[]) => {
someCallThatReturnsNothingButHasToBeDoneThere();
return entries;
})
.then((entries:EntryType[]) => console.log(entries))
Notes:
1) I try to avoid creating a function for that. My question was motivated by curiosity and just concerns what Vanilla ES6 or 7 syntaxes have to offer.
2) I was answered to use .forEach rather than .map. I design my code with a functional approach (hence the importance of compact callbacks), so .forEachis not necessarily a good choice for me (and apparently it doesn't have advantages over map in terms of performance or memory consumption). A one-line syntax is convenient both when handling promises callbacks or chains of array functions...
3) the returned type when using the OR operator is a union type, EntryType|null. So it breaks the typing for the subsequent calls and implies a type assertion:
SomeCallThatReturnsAPromise()
.then((entries:EntryType[]) => entries
.map(entry => (entry.isFeatured = true || entry) as EntryType)
.filter(entry => entry.something == true))
.then((entries:EntryType[]) => (someCallThatReturnsNothingButHasToBeDoneThere() || entries) as EntryType[])
.then((entries:EntryType[]) => console.log(entries))
That's getting heavier... I still don't know if I'll use that or stick with the two lines including the return statement.
4) That's a reduced example. I know that my first then contains a synchronous call or that my example could be more accurate.
entries.forEach( (entry) => entry.isFeatured = true );
No need to define the function separately.
Furthermore, as your elements are objects and are handled by reference, one can replace map() by forEach(), which removes the necessity to return a value.
(using map() you would end up with two arrays consisting of the same elements, which probably is not, what you need)
You can do what #Sirko wrote but return it like so:
SomeCallThatReturnsAPromise.then(entries => entries.forEach(entry => entry.isFeatured = true) || entries)
There's no need to use map, instead using forEach will give you a "one-liner", but then you want to return the same value that you received, using logical or (||) you can do that.
although #Sirko is right and, in that specific case, forEach has more sense than using map I think the OP was asking a generic question.
So, in general, how do you assign a property and then return the whole object? this is my suggestion:
function mutateObject(element, value) {
return (element.prop = value) && element || element
}
var object = {prop:1}
var returned = mutateObject(object, 2)
console.log(returned)
How does it work? the first part (element.prop = value) assigns the value to the property and return the value to the expression.
If the value returned is falsy, the value of the || clause is returned. If it's truthy the value of the && is returned.
In this case we return the element itself both times to be sure that's the object it will always be returned, no matter what we set in the property.
Another way to write that is (element.prop = value) ? element : element but, with this syntax, it looks more like a typo with the assignment instead of the comparison so I like the other syntax better.
//Example 1
const gimmeSmile = {}
console.log({ ...gimmeSmile, smile: ":)" })
// Example 2
const smiles = [{},{},{}]
.map(obj => ( { ...obj, smile: ":)"} ));
console.log(smiles)
Say you have an array of unique values and want to push new elements from another array, that meet a condition, without creating duplicates. E.g.
newArray.forEach(function(element){
if (condition) {
oldArray.push(element);
}
})
With regards to performance in Javascript, is it better to check, in every iteration of the loop, if the element exists already before pushing to the array, or to add all the elements that meet the condition, and then run _.uniq from underscore.js?
newArray.forEach(function(element){
if (condition && !oldArray.includes(element)) {
oldArray.push(element);
}
})
versus:
newArray.forEach(function(element){
if (condition) {
oldArray.push(element);
}
})
oldArray = _.uniq(oldArray);
Maybe it doesn't really make a difference for small projects (and arrays), but I want to know what's best for a large scale project.
_.uniq(oldArray);
will do an other loop of the array, so assuming the arrays are made of thousands elements surely the first solution is better.
Probably more usefull is to use indexOf instead of includes, infact, inside the includes funcion an indexOf is made:
newArray.forEach(function(element){
if (condition && oldArray.indexOf(element)===-1) {
oldArray.push(element);
}
})
How you can see, the includes prototipe is:
String.prototype.includes = function(search, start) {
'use strict';
if (typeof start !== 'number') {
start = 0;
}
if (start + search.length > this.length) {
return false;
} else {
return this.indexOf(search, start) !== -1;
}
};
An elegant solution would be to not use an Array but an object. Use key and value the same or your value as key and "true" as value:
{
"value":"value"
}
or
{
"value": true
}
while you're working with it. When you need the array of keys, convert with "for (p in obj)" to an array.
That way, operations in the array are unique by default without additional effort and only returning the array uses some calculations.
If your're using underscore or lodash, you can use _.keys(obj) as well.
I think I really just need a second pair of eyes here. I am having some issues with returning the value of a substring. I have a tweet that I have split into an array of words and then I am using array filter to find the Twitter handle. When I do find the handle, I want to make sure that there is no ":" on the end of the tweet.
When I console log the value that I am trying to return, I get the Twitter handle with no colon on the end. The returned value seems to still have the colon. Take a look below. The Twitter handle has to make it through all the logic in order to be returned.
getTweetedBy: function(keywords) {
// Assume keywords is equal to ['#AP:', 'this', 'is', 'a', 'tweet']
return keywords.filter(function(el){
if(el.substring(0, 1) === '#') {
if(el.slice(-1) === ':') {
// the value logged here is "#AP" as it should be
console.log(el.substring(0, el.length - 1));
return el.substring(0, el.length - 1);
}
}
});
}
When I run the code below, the console is logging ["#AP:"]. I need to remove the colon.
filterKeywords = commonFilters.filterKeywords(keywords);
tweetedBy = commonFilters.getTweetedBy(keywords);
storyLink = commonFilters.getTweetLink(keywords);
// The console is logging ["#AP:"]
console.log(tweetedBy);
Any help would be greatly appreciated.
Thanks!
EDIT:
As noted below by David, filter is expecting a truthy or falsey statement to be returned. Can anyone think of a method that is better than filter? Only want to return one value. I know I can do this with a loop, but a method would be better.
Thanks!
You want to separate your filtering and mapping functions. The first filter removes elements that don't match, and the second map transforms those matched values to whatever substring you want.
getTweetedBy: function(keywords) {
// Assume keywords is equal to ['#AP:', 'this', 'is', 'a', 'tweet']
return keywords
.filter(function(el){
return (el.substring(0, 1) === '#' && el.slice(-1) === ':');
})
.map(function(el){
// the value logged here is "#AP" as it should be
console.log(el.substring(0, el.length - 1));
return el.substring(0, el.length - 1);
});
}
Edit: Want it in one function? Here you go:
getTweetedBy: function(keywords) {
// Assume keywords is equal to ['#AP:', 'this', 'is', 'a', 'tweet']
return keywords
.reduce(function(matched, el){
if (el.substring(0, 1) === '#' && el.slice(-1) === ':') {
return matched.concat([ el.substring(0, el.length - 1) ]);
}
return matched;
}, [])
}
filter expects a function that returns truthy/falsey value.
It doesn't collect the values returned by the supplied function, it collects the elements for which the function is truthy. There are a bunch of options, including collecting the matched elements with the additional processing your requirements dictate.