Concating inside a map React - javascript

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

Related

TypeScript - Loop return gets skipped and base condition is returned

I have a custom object that is defined as following (names and properties have been simplified to avoid confidential information leakage):
type T = {
id: number;
title: string;
};
This type is used to create a list of Ts like so:
const IT: StringMap<T> = {
a: {
id: 0,
title: 'foo',
},
b: {
id: 1,
title: 'bar',
},
c: {
id: 2,
title: 'foobar',
},
}
I need a way to be able to retrieve one of these T type objects based on their id. So I created the following function:
const getTUsingId = (ts: StringMap<T>, id: number): T => {
Object.values(ts).forEach((t: T) => {
if (t.id === id) {
return t
}
});
// This is as a safeguard to return a default value in case an invalid id is passed
return ts['a']
}
For whatever reason, this function will always return ts['a'] regardless of what id I pass to it. Console logging t.id === id even returns true but it still carries on until the end of the ts map!
Any help is highly appreciated!
return won't break out of your loop. You need to use a plain for loop:
const getTUsingId = (ts: StringMap<T>, id: number): T => {
for (const t of Object.values(ts)) {
if (t.id === id) {
return t
}
}
return ts['a']
}
the forEach callback is for performing an action on every element in the array, it's return isn't used for termination, what you want is filter, find or a regular for loop
Object.values(ts).filter((t: T) => {
return t.id === id;
});
return all values where the id matches as a fresh array, you would use this if there was a possibility of multiple match, you would then need to parse the returned array to decide which match was correct
Object.values(ts).find((t: T) => {
return t.id === id;
});
return only the first matching value
for(const t of Object.values(ts))
if (t.id === id) {
return t
}
});
my preference would be find
const getTUsingId = (ts: StringMap<T>, id: number): T => {
return Object.values(ts).find((t: T) => t.id === id) ?? ts['a']
}

.map function continue running after Return

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.

Find matching item across multiple store arrays in VueX

Currently when I want to find single item in an array that is in store I use this:
this.matched = this.$store.state.itemlist.find(itemId=> {
return itemId.id == "someid";
});
Lets says I want to go over multiple arrays to find the matching item given provided ID? Like i have itemlist1 itemlist2 itemgetter()... Some of the arrays are getters ( but I think it doesnt change much). So basically I want to search over different state and getter items in this component instead of searching over one as in example above.
if you just want to find if its exist in one the arrays you can simply write function like this
function find(search,...arrs){
return arrs.flat(1).find(item => item == search)
}
this function merge all arrays to one long array and search in it
example of usage
let a=[1,2,3,4]
let b=[5,6,7,8]
let c=[9,10,11,12]
let i=find(6,a,b)
console.log(i)
Using one object to group all the arrays, so that will be possible to iterate over them. The idea is something like below:
const store = new Vuex.Store({
state: {
itemsGroupArrays: {
items1: [{ id: 1, text: "item1 - 1" }, { id: 2, text: "item1 - 2" }],
items2: [{ id: 3, text: "item2 - 1" }, { id: 4, text: "item2 - 2" }]
}
},
getters: {
getItemByIdFromStateGroupArrays: state => (id) => {
let returnedItem = null;
Object.values(state.itemsGroupArrays).forEach((itemStateArray) => {
if (itemStateArray.some(item => item.id === id)) {
returnedItem = itemStateArray.find(item => item.id === id);
}
})
return returnedItem;
}
}
});

React: Calling filter on Object.keys

A React component is passed a state property, which is an object of objects:
{
things: {
1: {
name: 'fridge',
attributes: []
},
2: {
name: 'ashtray',
attributes: []
}
}
}
It is also passed (as a router parameter) a name. I want the component to find the matching object in the things object by comparing name values.
To do this I use the filter method:
Object.keys(this.props.things).filter((id) => {
if (this.props.things[id].name === this.props.match.params.name) console.log('found!');
return (this.props.things[id].name === this.props.match.params.name);
});
However this returns undefined. I know the condition works because of my test line (the console.log line), which logs found to the console. Why does the filter method return undefined?
Object.keys returns an array of keys (like maybe ["2"] in your case).
If you are interested in retrieving the matching object, then you really need Object.values. And if you are expecting one result, and not an array of them, then use find instead of filter:
Object.values(this.props.things).find((obj) => {
if (obj.name === this.props.match.params.name) console.log('found!');
return (obj.name === this.props.match.params.name);
});
Be sure to return that result if you use it within a function. Here is a snippet based on the fiddle you provided in comments:
var state = {
things: {
1: {
name: 'fridge',
attributes: []
},
2: {
name: 'ashtray',
attributes: []
}
}
};
var findThing = function(name) {
return Object.values(state.things).find((obj) => {
if (obj.name === name) console.log('found!');
return obj.name === name;
});
}
var result = findThing('fridge');
console.log(result);
You need to assign the result of filter to a object and you get the result as the [id]. You then need to get the object as this.props.things[id]
var data = {
things: {
1: {
name: 'fridge',
attributes: []
},
2: {
name: 'ashtray',
attributes: []
}
}
}
var name = 'fridge';
var newD = Object.keys(data.things).filter((id) => {
if (data.things[id].name === name) console.log('found!');
return (data.things[id].name === name);
});
console.log(data.things[newD]);

Why does the following map function produce undefined objects?

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.

Categories