When clicking on a button i call a function to set a unique ID of an object and then push the object inside an array:
const addItems = (item) => {
item.uniqueId = addedItems.length + 1;
addedItems.push(item);
};
It works as intended if i add the items slowly, but if i press multiple times a second on the button the unique id will be the same (and a wrong one, not the one in line: for example if i have 2 items in the array with unique id of 1 and 2 and then press fast on the button 3 times, those 3 objects will have a unique id of 5, all 3 of them). What is wrong here?
Assuming it is state you must always update it in immutable way:
setAddedItems(ps=>[...ps,{...item,uniqueId:ps.length+1}])
And indeed this way as mentioned in other answer if you remove items, then add new ones, the ids may repeat; but if items aren't removed, then this approach is ok.
You can also check uuid if you want to remove items too.
Your code have many minuses, I recommend you use nanoid.
If you will remove item from your useState, some ids could be same. Better use an index for this if you would not change your state (remove items)
It will look like:
const addItems = (item) => {
setAddedItems((addedItems) => [
...addedItems, { ...item, uniqueId: nanoid() }
])
}
Related
I am not able to correctly delete or sort dynamically created component in Blazor.
I create my objects as follows in my page body:
private RenderFragment CreateComponentObject() => builder =>
{
foreach (var item in myObjects)
{
builder.OpenComponent(i, typeof(MyBlazorComponent));
builder.AddAttribute(1, "ComponentID", item.ID);
builder.AddAttribute(1, "ComponentName", item.Name);
builder.AddComponentReferenceCapture(i, inst => { ReferenceObjects.Add((MyBlazorComponent)inst); });
builder.CloseComponent();
}
i++;
};
Now I will need to both be able to
1. Sort the components by ID or Name
2. Eventually remove one or more components
I tried to sort the items through a simple sort script, but it does not work. Alternatively, I 've tried to "remove" all the components through builder.Clean and to re-enter in the CreateComponentObject function, ordering the components to be created in the desired way.
But this strategy does not work well, since the order changes for some parameters and for others it doesn't.
Example: if I clear the builder and re-allocate components by name, I will see the ID of the component following the right order, but the name will not change
Before:
ID 19 Name Kiki
ID 20 Name Jiji
After:
ID 20 Name Kiki
ID 19 Name Jiji
Does anyone have an idea how to face my two issues?
Numbering should follow linenumbers, not for loops.
builder.OpenComponent(1, typeof(MyBlazorComponent));
builder.AddAttribute(2, "ComponentID", item.ID);
builder.AddAttribute(3, "ComponentName", item.Name);
builder.AddComponentReferenceCapture(4, inst => { ReferenceObjects.Add((MyBlazorComponent)inst); });
builder.SetKey(item); // makes the sequence nrs less relevant
builder.CloseComponent();
// i++;
But you probably want to use SetKey to override the numbering.
I am learning React and just created a simple todo app using only React. My todo app has the standard structure of having a text input and an "ADD" button next to it. The user would type their todo in the input and every time they click on the "ADD" button next to it, a new ordered list of their inputs would appear underneath the input and "ADD" button.
The user can also delete a todo entry by clicking on the entries individually, like this:
To accomplish this behaviour of deleting entries, I used this delete function:
delete(elem) {
for (var i = 0; i < this.state.listArray.length; i++) {
if (this.state.listArray[i] === elem) {
this.state.listArray.splice(i, 1);
this.setState({
listArray: this.state.listArray
});
break;
}
}
}
My todo app works exactly the way that I want it to work, but as I look at other people's more conventional approach to this delete function, they either just simply use the splice method or the filter method.
For the splice method approach, they apparently just simply "remove" the unwanted entry from the listArray when the user clicks the particular entry. This does not work for me as using this method results in all my entries getting deleted except for the entry that I clicked on, which is the one that I want to delete.
On the other hand, the filter method approach apparently works by comparing the elem, which is the data passed from a child component, with each element in the listArray, and if the element in the for loop does not equal to the elem, then it would be passed onto a new array. This new array would be the one to not be deleted. This approach works better than the simple splice approach, however, one problem that I had encountered with this approach is that if I have more than one entry of the same value, for example, "Feed the dog". I only want one of the "Feed the dog" entries to be deleted, but it deletes both of them.
I thought of an approach to tackle this problem, eventually coming up with the current version of my code, which uses the splice method, but the splice method is used before I set it in the state. As evident here:
this.state.listArray.splice(i, 1);
this.setState({
listArray: this.state.listArray
});
My question can be broken down into three subquestions:
Considering that React states should be immutable, is the first line of the code above mutating my state? Is this approach not okay?
I thought that all React states were only possible to be changed inside a "setState" function, but my first line of code from above is not inside a setState function, yet it changed the state of listArray. How is this possible?
If my approach is mutating the state and is not ideal, how would you go about making the delete function so that it only deletes one entry and not more than one if there are multiple similar entries?
Yes, splice affects the array it acts on so don't use in this way. Instead you need to create a new array of the correct elements:
this.setState({
listArray: this.state.listArray.filter((el, idx) => idx !== i);
});
If you want to remove only the first instance, maybe couple with a findIndex (although indexOf would work in your example as well) first:
delete(elem) {
const idxToFilter = this.state.listArray.findIndex(el => el === elem);
if (idxToFilter < 0) {
return;
}
this.setState({
listArray: this.state.listArray.filter((el, idx) => idx !== idxToFilter);
});
}
This creates a new array without modifying the old which will cause anything that reacts to listArray changing to be notified since the reference has changed.
I have a function that find an object from a JSON that has an id === this.match.mainParticipant.stats.perkSubStyle. This object contains a property called slots that is an array and has 4 elements. Each slot has 3 elements which represent runes from a game. If you iterate over the slots and their elements you get this:
I get the object using this function:
secondaryPerks(){
let perksTree = this.$store.state.summonerRunes.find(value => value.id === this.match.mainParticipant.stats.perkSubStyle);
console.log(perksTree.slots.unshift())
return perksTree
}
and I iterate and display the icons using this:
<div v-for='runes in this.secondaryPerks().slots'>
<div v-for='rune in runes.runes'>
<img :src="'https://ddragon.leagueoflegends.com/cdn/img/' + rune.icon" alt="">
</div>
</div>
Now the problem is that because that perks tree is secondary one, the perks in slot[0] can never be picked because if they were picked, they'd have to be part of the primaryPerks tree. This means there's no point displaying that none of them were selected. For that reason I am trying to remove the first slot[0] element from the array, however, when I try to unshift() it, I get an error:
"You may have an infinite update loop in a component render function"
And I have no clue why. Any advices?
Firstly, I think you mean shift rather than unshift. unshift will try to add items to the array rather than removing them. It doesn't actually matter from the perspective of the infinite loop, either method will have the same effect.
You're creating a dependency on the array and then modifying it. Modifying it will trigger a re-render.
Each time the component re-renders it will shift another item onto/out of the array. Even if the call to shift/unshift doesn't actually change anything it will still count as modifying the array.
Try:
computed: {
secondaryPerkSlots () {
const perksTree = this.$store.state.summonerRunes.find(
value => value.id === this.match.mainParticipant.stats.perkSubStyle
);
return perksTree.slots.slice(1)
}
}
with:
<div v-for='runes in secondaryPerkSlots'>
That will create a new array containing the same elements as the original array, omitting the first element.
Alternatively you could put the slice(1) directly in the template:
<div v-for='runes in secondaryPerks().slots.slice(1)'>
Either way I suggest changing the method to a computed property instead. You should also drop the this in your template.
I had the same problem a few months ago.
I think the main issue is that you perform logic such as arr.unshift()(which will cause the template to re-render in this case) in your computed property.
So, imagine this:
const arr1 = [/* ... */];
// This is different
const computedArr = () => {
return arr.filter(() => { /* ... */ });
};
// Than this
const computedArr = () => {
const newArr = arr.filter(() => { /* ... */ });
// Vue cannot allow this without a re-render!
newArr.unshift();
return newArr;
};
The latter will cause the template to re-render;
EDIT
Check the first comment!
It's quite possible I am going about this the wrong way, but I have a primary array that i need to filter if any of it's objects values exist in two other arrays. I am trying to use a combination of filter() and some() but what I have right now is not working.
const milestones = <FormArray>this.piForm.get('_milestones');
if (this.piById) {
milestonesToCreate = milestones.value
.filter(milestone => !this.piById.milestones.some(item => item.milestoneId === milestone.milestoneId));
milestonesToDelete = this.piById.milestones
.filter(milestone => !milestones.value.some(item => item.milestoneId === milestone.milestoneId));
milestonesToUpdate = milestones.value
.filter(milestone => milestones.value
.some(item =>
item.milestoneId === milestonesToCreate.milestoneId && milestonesToDelete.milestoneId));
}
In the code above milestonesToUpdate should be a the filtered results where the array consists of objects that are not in milestonesToCreate and milestonesToDelete
Hopefully I've explained this well enough.
ADDED SAMPLE MILESTONES ARRAY
milestones = [
{
"milestoneId": 0
}
]
Firstly, it looks like your problem is just a misunderstanding of boolean checks in your final call to some().
You have put:
item.milestoneId === milestonesToCreate.milestoneId && milestonesToDelete.milestoneId
Which is the same as saying, where item.milestoneId equals milestonesToCreate.milestoneId AND milestonesToDelete.milestoneId exists. I expect that you are just trying to check if the current value exists in both arrays.
it's better to achieve that in single pass:
put all elements to elementsToUpdate, copy all elements from your this into elementsToDelete
iterate through elementsToUpdate, once some item does not exist in another list, move that element into elementsToCreate
if element exists in both, remove it from elementsToDelete.
finally you will get 3 lists you need.
And you can even speed up code more if instead of using arrays you use hash(old good {}) where id are used as keys. Then check "if element is here" would be as easy as item in elementsToUpdate instead of iterating all the elements each time
I've a little array problem I'm going round and round in loops with.
I have a number of check boxes, each checkbox has a specific object.
On checking a checkbox I want to iterate over the array and if the id of the checkbox (object) does not match any other items in the array, push to the array.
I have the following, which pushes the checkbox object for every item that doesn't match it's id. So I end up with multiple objects of the same ID.
mapMarkers.map(marker => {
if(markerID !== marker[0].id) {
mapMarkers.push(markerObject)
};
});
Any help to get my thinking on this straight would be appreciated.
For context here's the project its from. Lines 281
https://codepen.io/sharperwebdev/pen/PQvMqR?editors=0011
The Array#filter method would be more appropriate for this. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
const filteredMarkers = mapMarkers.filter(marker => markerID !== marker.id);
Then use filteredMarkers (mapMarkers isn't mutated, which is a better practice).