Array methods returning undefined - javascript

This is something I still don't quite understand very well, I have the following situation:
// #2) Check if this array includes any name that has "John" inside of it. If it does, return that
// name or names in an array.
const dragons = ['Tim', 'Johnathan', 'Sandy', 'Sarah'];
at first I tried the following:
const name = dragons.forEach(element => {
element.includes("John") ? element : null
});
it returns undefined
then:
const name = dragons.filter(element => {
element.includes("John");
});
it returns an empty array
then:
function name() {
const dragons = ['Tim', 'Johnathan', 'Sandy', 'Sarah'];
dragons.forEach(element => {
if (element.includes("John")) {
return element;
}
});
}
again it returns undefined
but the interesting thing is that if on any of the attempts I change the action to do to console.log(element); then it will show "Johnathan" which is the correct output.
so it means that the logic is working, I just don't understand why it won't return the value if I want to let's say assign it to a variable.
I even tried the following:
const dragons = ['Tim', 'Johnathan', 'Sandy', 'Sarah'];
dragons.forEach(element => {
if (element.includes("John")) {
const name = element;
}
});
but it again returns name as undefined, why is that?
edit: this was my first stack overflow question, really I wasn't expecting someone answering my question, I thought maybe it was a dumb question or that i didn't explained myself well but it was very nice to find such a supportive community, thank you so much to all of you who commented and helped me!

forEach method does return undefined. But filter instead returns the array but filtered. However, the problem is that you are forgetting to return the values inside it.
To fix it try:
const name = dragons.filter(element => {
return element.includes("John");
});
Or:
const name = dragons.filter(element => element.includes("John"));

First, forEach never returns anything. You'd need a map for that, or filter (what are you exactly after?). Second, your callback functions don't return anything, either. You can map or filter your array like that:
const dragonsNamedJohn = dragons.filter(name => name.includes('John'));
or
const dragonsNamedJohn = dragons.filter(name => {
return name.includes('John');
});
or even
function hisNameIsJohn (name) {
return name.includes('John');
}
const dragonsNamedJohn = dragons.filter(hisNameIsJohn);
(You might want to stick toLowerCase() somewhere there, or not, dependent on whether you want the comparison to be case-sensitive. Also, another way of skinning a dragon would be to use a regular expression).

Related

How to declare two actions in ? : format?

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

What is "el", what relation do these codes have with each other, and how does the fat arrow work?

I need to understand what the code means, or a better break down of how/why it works.
This function should find an object in an array called items by it's id number, and return the objects properties of which there are 9 for each object.
I've googled how the fat arrow works, but the examples given don't match this code very well, or I'm still to ignorant to see the relation. I also don't COMPLETELY understand how el is being used. I assume it's a place holder for "item" or a Boolean. I feel it's on the tip of my tongue.
pantry{
//.. other code of an empty array, and a function that fills the array at run time
getItem: function(id) {
return this.items.find(el => {
return el.id == id
});
}
}
My tutor showed me that this code is equivalent to
for(var i = 0; i < items.length; i++){
let item = items[i];
if(item.id == id){
return item;
}
This code works as is, but I'm just barely not understanding what el does, I've changed it to ed, and do and se, and it still works, but I'm trying to understand deeply.
I'm very new to JavaScript. I've been reading "You Don't Know JavaScript" for a month now, and practicing for 3 months. I don't know if I'm learning in the best way, but I put work into learning every single day --- at any rate I digress.
Thank you for any amount of your time.
el is just a variable. When you pass it into a function like find, it represents the element of the array that you are looking at during that iteration.
The arrow is just another way of defining a function. Broadly speaking, these two functions are equivilant:
(el) => {
return el.id == id
});
function(el) {
return el.id == id
}
You can read more about them and the differences between arrow and normal functions here.
for beginner simple function and arrow function are same it's just the short way of doing stuff i. e
function(param) {} = (param) => {} = param => {}
what array find function (in your case array is items) does is that it pass every item to the function and return only those item which pass the condition or return true i.e its id is equal to the given id e.g
arr = [{id: 2, name: 'temp'}, {id: 1, name: 'other'}]
on first iteration el is equal to {id: 2, name: 'temp'} on second iteration its equal to {id: 1, name: 'other'} now if the given id is 2 it will return {id: 2, name: 'temp'} because it pass the condition or it return true now point to note is the el is just a name of a variable you can replace it with any name like you can replace id with other variable like tempId in getItem: function(id) but you have to give it only one parameter because the find function expect only one argument

How to check if an object's keys and deep keys are equal, similar to lodash's isEqual?

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

Javascript - How to place a statement inside expression?

I'm learning javascript and I have a problem I stumbled upon. I have the following javascript code (I'm searching for the name john inside MyArray, which contains class objects):
MyArray.forEach(
(v,i)=>(
v.name == 'john' && return i
)
);
This is not working, because I get the following error for the 3rd line:
SyntaxError: expected expression, got keyword 'return'
My question is, how can I put statement inside an expression?
How can i put statement inside an expression?
You can't, currently. Nor, in that example, do you want to. The return value of the forEach callback isn't used for anything.
See below the horizontal rule for your specific case, but answering the question you actually asked:
On occasion, wanting to do some statement logic and then have an overall expression take the final result of that statement logic does come up. Consequently, there's a proposal for do expressions working its way through the JavaScript proposals process. It's only at Stage 1 of the process, and so it may not progress, or if it progresses the form of it may change, but for now it looks like this:
let x = do { // Using the `do` expression proposal
let tmp = f(); // (currently just Stage 1, do not use!)
tmp * tmp + 1
};
...where x would get the result of that last expression within the {}.
If you really wanted to do that now, you'd probably use an immediately-invoked arrow function:
let x = (() => {
let tmp = f();
return tmp * tmp + 1;
})();
The purpose of do expression proposal is to make that less verbose, when you need to do.
(Please don't mistake this for my offering an opinion on do expressions either way. I'm just noting the proposal exists and is working through the process.)
Your specific case of finding the index of an item:
In your specific case, you're looking for the Array#findIndex function, which returns the index of the entry where the callback returns a truthy value:
const index = MyArray.findIndex(v => v.name == 'john');
(Or you might want Array#find if you want the entry itself instead of its index.)
Note that since that uses a concise arrow function, the return value of the function is the value of the expression forming the function body. We could use a verbose one instead with an explicit return:
const index = MyArray.findIndex(v => { return v.name == 'john'; });
Looks like you want to find the index of the Mr. John? Then use dedicated method findIndex for this:
const john = MyArray.findIndex(
(v,i) => (
v.name == 'john'
)
);
You cant. Just do:
for(const [index, el] of myArray.entries())
if(el.name === "john") return index;
Or more easy:
return myArray.findIndex(el => el.name === "john");
forEach is just a loop of all elements. You can't return anything there like you did. You could use find for example, if you want the object with the name john:
var MyArray = [
{name: 'test', some: 'data'},
{name: 'john', some: 'data'},
];
var obj = MyArray.find(v => v.name == 'john');
console.log(obj);

forEach vs for loop?? for loop seems to work, but not with forEach. Why? Please can someone explain?

/*please have a look at the following function. Its a simple function. I want to iterate over the movies array and return the element; only if the element's title is exactly same as the argument passed in. otherwise return false at the end of the iteration.
The problem is, it always return false. However, if I use a regular forloop instead of forEach loop, it works perfectly fine.. can someone please explain why is this situation?????? Thank You in advance.
*/
function searchMovies(title) {
movies.forEach(function(ele){
if(ele.title === title){
return ele;
}
});
return false;
}
//movies array
var movies = [
{title: 'The Hobbit'},
{title: 'The Great Gatsby'},
{title: 'Gone with the Wind'}
];
//the following line calls the function
searchMovies('The Great Gatsby');
You're returning from inside the callback passed to forEach which forEach ignores every time and call the callback to the next element. What you need is to use find like this:
function searchMovies(title) {
var found = movies.find(function(ele){
return ele.title === title;
});
return found; // found will be either and item from the array (if find found something) or undefined (if it doesn't)
}
Note 1: the movies array should be either defined before the function searchMovies, or passed to it as a parameter (the best approach).
Note 2: if you want to return an array of all the matched element (if there is duplicates in the array and you want to return all of them), then use filter, which is used the same way and it return an array of all the matched elements (an empty one if nothing matched).
Because you're returning inside the forEach function.
function searchMovies(title) {
var foundMovie = false;
movies.forEach(function(ele) {
if (ele.title === title) {
foundMovie = ele;
}
});
return foundMovie;
}

Categories