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.
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 object that looks something like this:
const state = []
const spells = [
{
name: 'test',
channelDuration: 1000,
castDuration: 1000,
cast: () => {
state.push(THIS) // <--
}
}, {...}, {...}
]
When a player uses a spell, that spell gets stored into the game state and then waits for the channelDuration and castDuration to pass.
Once able to cast, it looks into the state and calls the cast method:
State.quest.bossFightState[USER].spell.cast()
state = [Spell]
On said cast, i need to push the spell into the a new array called statuses. However, i can't reference the object within the cast method without using some additional helper functions to grab the specific spell. And I dont want to pass add a parameter spell into the cast method because that just seems dumb.
Any ideas? Or is a helper method the way to go here.
I'm surprised nobody else caught this... You're using this inside of an arrow function. The this keyword acts differently in arrow functions - read more about that here
const state = [];
const spells = [
{
name: 'test',
cast: function () {
state.push(this);
},
},
];
spells[0].cast();
console.log(state);
One option would be to use a function or method instead of an arrow function, so that .cast will have a this referring to the outer object.
const state = []
const spells = [
{
name: 'test',
cast() {
state.push(this)
}
}
]
spells[0].cast();
console.log(state);
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.
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've create an Http request to get json data. Inside that json - there is an object which has an array. ( I need that array).
fromDb$ = of({
Result: {
Countries: [{ <--wanted array
ISOCode: 1,
Name: 'aaa'
}, {
ISOCode: 2,
Name: 'bbb'
}]
}
});
But- the data in the array has a different structure than I actually need.
I need to map (name &ISOcode) to (name and value )
This is what I've tried:
Use pluck to extract the inner Array
mergeMap the array object to a stream of objects (using of())
using map to transform each item to a desired structure
using toArray to wrap all to an array ( so I can bind it to a control)
Here is the actual code :
this.data = this.fromDb$.pipe(pluck<PtCountries, Array<Country>>('Result', 'Countries'),
mergeMap(a => from(a)),
map((c: Country) => ({
name: c.Name,
value: c.ISOCode,
})),
toArray());
The code does work and here is the online demo
Question
It looks like I've complicated it much more than it can be ,Is there a better way of doing it?
This line: mergeMap(a => from(a)) does not make a lot of sense. It's almost as if you did [1,2,3].map(v => v). You can just remove it.
To simplify this you basically need to use Array.map inside Observable.map.
Try this:
this.data = this.fromDb$.pipe(pluck<PtCountries, Array<Country>>('Result', 'Countries'),
map((countries: Country[]) => countries.map(country => ({
name: country.Name,
value: country.ISOCode,
}))));
Live demo
this.data = this.fromDb$.pipe(
mergeMap(object => object.Result.Countries),
map(country => ({ name: country.Name, value: country.ISOCode })),
toArray()
);