I'm running a basic .map function and an if-else inside and I want that if it found something it returns and stops the loop but it's continuing and I don't understand why I believe the response is pretty simple.
The function :
const isInFieldsTemplate = (fields, field) => {
fieldsTemplate.map((item) => {
if (field === item.label) {
console.log(field + 'IIIIIIIIIII' + item.label);
return true;
}
});
return false;
};
Data Example:
field: Quantity
fields:
[
{ label: 'createdBy', value: 'createdBy' },
{ label: 'Quantity', value: 'Quantity' },
{ label: 'location', value: 'location' },
]
So if you run this function with this data the loop will not stop at Quantity and return true it will simply continue and return false.
The point of map is to create a new array with a transformed version of everything in the original array.
If you aren't using the return value (the new array) from map then you shouldn't be using map.
If you want to search for the first item in an array that matches some condition: use find, not map.
Related
I am trying to use concat inside a mapping function. In this code snipped you see my function which first checks if there are any chips in the chips array. If not, a chip is created. This part works fine. If there are chips and the ID of the chip matches the ID of my target, I only change the displayed text and don't create another chip. This is also fine, but I would expect, that if this isn't the case, that I would be able to concat another chip, but this doesn't work. I also get not errors and when I log the last part it shows that a chip gets added to the array.
Am I missing something really simple here ? I would like to provide more code, but my project has lots of imports and stuff which would make this a very long post. Thanks for any help :)
const onAddBtnClick = (e) => {
setChipsActive(true);
setChips(
chips.length === 0
? chips.concat({
key: chips.length,
label: e.target.value.toUpperCase(),
id: e.target.name,
})
: chips.map((obj) => {
if (obj.id === e.target.name) {
return { ...obj, label: e.target.value.toUpperCase() };
} else {
chips.concat({
key: chips.length,
label: e.target.value.toUpperCase(),
id: e.target.name,
});
}
return obj;
}),
);
};
The js function concat exists on arrays and can only be used with other arrays.
So to fix your issue I would recommend you wrap the object you're trying to concat with an array, like so
chips.concat([{
key: chips.length,
label: e.target.value.toUpperCase(),
id: e.target.name,
}]);
important to note that this only returns an array where both arrays are concatenated, if you want to push the object into an array i would use
chips.push({
key: chips.length,
label: e.target.value.toUpperCase(),
id: e.target.name,
});
instead.
What solved the issue for me was to take the array manipulation outside of the mapping and write another condition for it like this:
const onAddBtnClick = (e) => {
setChipsActive(true);
const obj = {
key: chips.length,
label: e.target.value.toUpperCase(),
id: e.target.name,
};
const newChips = (prevChips) => [...prevChips, obj];
const updateChips = chips.map((obj) => {
if (obj.id === e.target.name) {
return { ...obj, label: e.target.value.toUpperCase() };
} else {
return obj;
}
});
const hasChip = chips.some((el) => el.id === e.target.name);
if (!hasChip) {
setChips(newChips);
} else {
setChips(updateChips);
}```
I have an array of javascript objects,
let headers = [
{
text: 'something',
value: 'something else'
},
{
text: 'something1',
value: 'something else1'
},
// etc..
]
I want to loop through the array and add a method to each object in the array like so (ignore "this", I'm using vue):
this.headers.map(h => {
h['filter'] = function (value) {
if (!this.filterValue) {
return true;
}
return value.toLowerCase().includes(this.filterValue.toLowerCase());
}
});
It looks fine to me, but when my loop completes, the function does not work, and I think it has something to do with this error in "arguments" and "caller":
Any ideas on how to resolve this error?
the key thing is this, you need to learn more about this in JS, and it is asked in interviews a lot, run this snippet and figure how this is going
function A(headers = [{
text: 'something',
value: 'something else'
},
{
text: 'something1',
value: 'something else1'
},
// etc..
], filterValue = 'else1') {
this.filterValue = filterValue
this.headers = headers.map(h => ({
...h,
filter: () => {
if (!this.filterValue) {
return true;
}
return h.value.toLowerCase().includes(this.filterValue.toLowerCase());
}
}))
}
console.log(new A().headers.map(x => x.filter()))
I want to return only the matched item, I solved this problem creating my own high order function, I want to solve this in a completely functional way.
Is there any similar javascript function that does what my function is doing? See the examples below, I wrote some Jest based examples to facilitate what I am expecting.
The function will try to find the value until is different than undefined. If this kind of function does not exist what you guys think of trying implementing it on JavaScript, maybe making a tc39 proposal? Anyone had the same problem as me before?
I know how the Array.prototype.find works and why it does not work when chained to get deep elements.
There are some conditions that I would like to meet:
Return what my function returns and not the whole item if it's truthy.
For performance reasons, when the value is found there is no need to keep looping in the array, in the example below I used the condition anything different from undefined to exit the for loop.
Follow the standard of the others high order functions such as find, map, filter and reduce like this: fn(collection[i], index, collection).
const findItem = (collection, fn) => {
for (let i = 0; i < collection.length; i++) {
const item = fn(collection[i], i, collection)
if (item !== undefined) return item
}
return undefined
}
let groups = [
{ items: [{ id: 1 }, { id: 2 }] },
{ items: [{ id: 3 }, { id: 4 }] },
]
var result = findItem(groups, group =>
findItem(group.items, item => item.id === 4 ? item : undefined))
// works!
expect(result).toEqual(groups[1].items[1])
// Array.prototype.find
var result2 = groups.find(group =>
group.items.find(item => item.id === 4 ? item : undefined))
// returns groups[1], don't work! And I know why it does not work.
expect(result2).toEqual(groups[1].items[1])
Probably horrible, but you could make use of a backdoor in the reduce function that would allow you to exit early on a match
let groups = [
{ items: [{ id: 1 }, { id: 2 }] },
{ items: [{ id: 3 }, { id: 4 }] },
];
const item = groups.slice(0).reduce((val, g, i, arr) => {
for (const item of g.items) {
if (item.id === 4) {
val = item;
arr.splice(1); // exit
}
}
return val;
}, null);
item && console.log(item);
Note - use of slice is to ensure the original array is not mutated
So my call returns something like:
data:
{
nameData: 'Test33333',
emailData: email#email.com,
urlLink: link.com
additionalDetails: [
{
field: 'email',
value: 'other#email.com'
},
{
field: 'name',
value: 'name1223'
}
]
}
Now, I want to make a function that would take the passed parameter (data) and make an array of objects, that should look like below. It should be done in more generic way.
Array output expectation:
fullData = [
{
name: 'data_name'
value: 'Test33333'
},
{
name: 'data_email',
value: 'email#email.com'
},
{
name: 'data_url',
value: 'Link.com'
},
extraData: [
//we never know which one will it return
]
];
It should be done in the function, with name, for example:
generateDataFromObj(data)
so
generateDataArrFromObj = (data) => {
//logic here that will map correctly the data
}
How can this be achieved? I am not really proficient with JavaScript, thanks.
Assuming that you keep your data property keys in camelCase this will work for any data you add, not just the data in the example. Here I've used planetLink. It reduces over the object keys using an initial empty array), extracts the new key name from the existing property key, and concatenates each new object to the returned array.
const data = { nameData: 'Test33333', emailData: 'email#email.com', planetLink: 'Mars' };
function generateDataArrFromObj(data) {
const regex = /([a-z]+)[A-Z]/;
// `reduce` over the object keys
return Object.keys(data).reduce((acc, c) => {
// match against the lowercase part of the key value
// and create the new key name `data_x`
const key = `data_${c.match(regex)[1]}`;
return acc.concat({ name: key, value: data[c] });
}, []);
}
console.log(generateDataArrFromObj(data));
Just run a map over the object keys, this will return an array populated by each item, then in the func map runs over each item, build an object like so:
Object.keys(myObj).map(key => {return {name: key, value: myObj[key]}})
I have an array context.buildingFields.finished that looks like this:
[{ name: 'a' type: 'text', value: '1' }, { name: 'b' type: 'file', value: '2' }]
And I have a function that loops through that array to create a new one context.createPanoramasPayload with only the fields of type file:
function handleCreatePanoramas (uploadedPhoto, context, callback) {
const panoramasFields = context.buildingFields.finished
context.createPanoramasPayload = panoramasFields.map(field => {
if (field.type !== 'file') return
return {
name: 'index',
value: uploadedPhoto
}
})
callback(context.createPanoramasPayload)
}
}
I thought I would produce something like this (say with only one field of type file):
[{ name: 'b' type: 'file', value: '2' }]
However, what I'm getting is something like this:
[undefined, { name: 'b' type: 'file', value: '2' }]
Why is this? And how to modify the code to achieve what I want?
map returns an array that has the same length as the given array. It does not help to return just like that, as that will generate an undefined value in your mapped array. Instead you need to apply filter first:
context.createPanoramasPayload = panoramasFields.filter(field => {
return field.type === 'file';
}).map(field => {
return {
name: 'index',
value: uploadedPhoto
}
})
This keeps with a functional way of programming.
As a side note, since the callback functions now don't do anything else than return something, you can use the expression syntax for the arrow functions:
context.createPanoramasPayload = panoramasFields
.filter(field => field.type === 'file')
.map(field => ({
name: 'index',
value: uploadedPhoto
}));
You are using map function here, that means it will not reduce the length of the array. It return same length array. So use filter to get result.
This answer is an extension to #trincot's answer.
Using .filter + .map is not wrong, but it adds to extra iterations. Both these functions have their own specific use-case but when you need to do both, .reduce is more suited as you can manually parse object and return custom array/object.
Sample
context.createPanoramasPayload = panoramasFields.reduce(function(p, c) {
if (c.type === 'file')
p.push({
name: 'index',
value: uploadedPhoto
});
return p;
}, [])
Also note the use of anonymous function instead of arrow function(=>).
What arrow function does is it binds current context to the passed callback function, so that we can use this inside the callback. If you are using arrow function but not using this inside callback, you are wasting the efforts engine puts in binding context to the callback. For this reason, if you do not use this, in my understanding, its better to use anonymous function instead of arrow function.
Though its not wrong, but in my POV, it is a bad practice.