I have an array of objects. When my api executes the update method and saves, I'm am broadcasting an event through laravel-echo-server and attempting to mutate state with the update object. I'm trying for real-time updates. Everything but the actual mutation is going according to plan. Here is the beginning of it:
updateExample (state, example) {
state.examples.map(e => {
if (e.id === example.id) {
// entirely replace the old object with the new
}
return false
})
},
What is an ideal way to do this? I could also pop the old object out of the array and push a new one, but that seems wonky.
Nevermind, figured it out:
updateExample (state, example) {
let index = state.examples.findIndex(e => e.id === example.id)
if (index !== -1) {
state.examples[index] = new Example(example)
return
}
state.examples.push(new Example(example))
}
Thanks for looking at it!
Related
I have an issue with the states not updating properly (stale closure). I am first setting index 0 of activeCards and console logging it (the first entry in the log provided). This log returns the result of the state as it was prior to it being updated. Then I am setting index 1 of the same and console logging it (second entry in the log provided). The second log returns the result as it was prior to the second update (ie as it should've been logged after the previous update). How can I update a state immediately or else wait for it to be updated before I proceed?
useEffect(() => {
console.log(activeCards)
}, [activeCards])
if(condition){
temp = [...activeCards];
temp[0] = {index: index, url: cards[index]};
setActiveCards([...temp])
console.log(activeCards)
}
else{
temp = [...activeCards];
temp[1] = {index: index, url: cards[index]};
setActiveCards([...temp])
console.log(activeCards)
}
let temp = [activeCards];
This makes temp a 2d array since activeCards itself is an array. Maybe what youre intending to do is,
let temp = [...activeCards];
state not update immediately for performance. if you use functional component you can use the useEffect(()=>{//code run after state updated},[your state]) and it call every time your state update to new value. and if you are using class component you can use this.setState({someState:''}, () => {
//code run after state updated
});
You can do like this.
useEffect(() => {
console.log(activeCards);
}, [activeCards]);
let temp = [...activeCards];
temp[0] = { index: index, url: cards[index] };
if (!condition) {
temp[1] = { index: index, url: cards[index] };
}
setActiveCards([...temp]);
The best way to solve stale closures is to use useRef() instead of useState() for values which one requires to be updated frequently. Hence one will avoid having a large number of states being updated sequentially causing a stale closure.
I am trying to make a system where you would pass in a bunch of coins and they would get added to the state array (selectedTickers). Once I would set selectedTickers to another array, it would still print out the original array. Anyone know how to get this to print out the new array?
Code:
function addTickerToList(ticker) {
const exists = selectedTickers.filter((a) => a === ticker);
console.log(exists);
if (exists.length > 0) {
console.log("first");
let newList = selectedTickers.filter((a) => a !== ticker);
setSelectedTickers(newList);
console.log(selectedTickers);
} else {
console.log("second");
console.log(ticker);
selectedTickers.push(ticker);
}
}
setSelectedTickers is asynchronous.
You are logging selectedTickers before the function fully executed, try to log it on the web page to ensure that the re-render is done.
I'm developing a helpdesk tool in which I have a kanban view.
I previously used nested serializers in my backend and I managed to have everything working with a single query but it's not scalable (and it was ugly) so I switched to another schema :
I query my helpdesk team ('test' in the screenshot)
I query the stages of that team ('new', 'in progress')
I query tickets for each stage in stages
So when I mount my component, I do the following :
async mounted () {
if (this.helpdeskTeamId) {
await this.getTeam(this.helpdeskTeamId)
if (this.team) {
await this.getTeamStages(this.helpdeskTeamId)
if (this.stages) {
for (let stage of this.stages) {
await this.getStageTickets(stage)
}
}
}
}
},
where getTeam, getTeamStages and getStageTickets are :
async getTeam (teamId) {
this.team = await HelpdeskTeamService.getTeam(teamId)
},
async getTeamStages (teamId) {
this.stages = await HelpdeskTeamService.getTeamStages(teamId)
for (let stage of this.stages) {
this.$set(stage, 'tickets', [])
}
},
async getStageTickets (stage) {
const tickets = await HelpdeskTeamService.getTeamStageTickets(this.helpdeskTeamId, stage.id)
// tried many things here below but nothing worked.
// stage.tickets = stage.tickets.splice(0, 0, tickets)
// Even if I try to only put one :
// this.$set(this.stages[this.stages.indexOf(stage)].tickets, 0, tickets[0])
// I see it in the data but It doesn't appear in the view...
// Even replacing the whole stage with its tickets :
// stage.tickets = tickets
// this.stages.splice(this.stages.indexOf(stage), 1, stage)
},
In getTeamStages I add an attribute 'tickets' to every stage to an empty list. The problem is when I query all the tickets for every stage. I know how to insert a single object in an array with splice or how to delete one object from an array but I don't know how to assign a whole array to an attribute of an object that is in an array while triggering the Vue reactivity. Here I'd like to put all the tickets (which is a list), to stage.tickets.
Is it possible to achieve this ?
If not, what is the correct design to achieve something similar ?
Thanks in advance !
EDIT:
It turns out that there was an error generated by the template part. I didn't think it was the root cause since a part of the view was rendered. I thought that it would have prevent the whole view from being rendered if it was the case. But finally, in my template I had a part doing stage.tickets.length which was working when using a single query to populate my view. When making my API more granular and querying tickets independently from stages, there is a moment when stage has no tickets attribute until I set it manually with this.$set(stage, 'tickets', []). Because of that, the template stops rendering and raises an issue. But the ways of updating my stage.tickets would have worked without that template issue.
I could update the stages reactively. Here is my full code; I used the push method of an array object and it works:
<template>
<div>
<li v-for="item in stages" :key="item.stageId">
{{ item }}
</li>
</div>
</template>
<script>
export default {
data() {
return {
stages: [],
};
},
methods: {
async getTeamStages() {
this.stages = [{ stageId: 1 }, { stageId: 2 }];
for (let stage of this.stages) {
this.$set(stage, "tickets", []);
}
for (let stage of this.stages) {
await this.getStageTickets(stage);
}
},
async getStageTickets(stage) {
const tickets = ["a", "b", "c"];
for (let ticket of tickets) {
this.stages[this.stages.indexOf(stage)].tickets.push(ticket);
}
},
},
mounted() {
this.getTeamStages();
},
};
</script>
It should be noted that I used the concat method of an array object and also works:
this.stages[this.stages.indexOf(stage)].tickets = this.stages[this.stages.indexOf(stage)].tickets.concat(tickets);
I tried your approaches some of them work correctly:
NOT WORKED
this.$set(this.stages[this.stages.indexOf(stage)].tickets, tickets)
WORKED
this.$set(this.stages[this.stages.indexOf(stage)].tickets, 0, tickets[0]);
WORKED
stage.tickets = tickets
this.stages.splice(this.stages.indexOf(stage), 1, stage)
I'm sure it is XY problem..
A possible solution would be to watch the selected team and load the values from there. You seem to be loading everything from the mounted() hook, and I suspect this won't actually load all the content on demand as you'd expect.
I managed to make it work here without needing to resort to $set magic, just the pure old traditional vue magic. Vue will notice the properties of new objects and automatically make then reactive, so if you assign to them later, everything will respond accordingly.
My setup was something like this (showing just the relevant parts) -- typing from memory here, beware of typos:
data(){
teams: [],
teamId: null,
team: null
},
watch:{
teamId(v){
this.refreshTeam(v)
}
},
methods: {
async refreshTeam(id){
let team = await fetchTeam(id)
if(!team) return
//here, vue will auomaticlly make this.team.stages reactive
this.team = {stages:[], ...team}
let stages = await fetchStages(team.id)
if(!stages) return
//since this.team.stages is reactive, vue will update reactivelly
//turning the {tickets} property of each stage reactive also
this.team.stages = stages.map(v => ({tickets:[], ...v}))
for(let stage of this.team.stages){
let tickets = await fetchTickets(stage.id)
if(!tickets) continue
//since tickets is reactive, vue will update it accordingly
stage.tickets = tickets
}
}
},
async mounted(){
this.teams = fetchTeams()
}
Notice that my 'fetchXXX' methods would just return the data retrieved from the server, without trying to actually set the component data
Edit: typos
I have the following component to quickly configure stops on a delivery/pickup route and how many items are picked up and dropped
and this is the data model, note the 2 is the one next to 'a' on the previous image.
If a click the + or - button, in the first item, it behaves as expected,
But second item doesn't work as expected
I've already checke a couple of posts on object property update likes this ones
Is it possible to mutate properties from an arbitrarily nested child component in vue.js without having a chain of events in the entire hierarchy?
https://forum.vuejs.org/t/nested-props-mutations-hell-internet-need-clarification/99346
https://forum.vuejs.org/t/is-mutating-object-props-bad-practice/17448
among others, and came up with this code:
ADD_ITEM_TO_SELECTED_STOP(state, payload) {
let count = state.selectedStop.categories[payload.catIndex].items[payload.itemIndex].count;
const selectedCat = state.selectedStop.categories[payload.catIndex];
const currentItem = selectedCat.items[payload.itemIndex];
currentItem.count = count + 1;
selectedCat.items[payload.itemIndex] = currentItem;
Vue.set(state.selectedStop.categories, payload.catIndex, selectedCat);
},
and as the button event:
addToItem(item) {
this.$store.dispatch("addItemToSelectedStop", {
catIndex: item.catIndex,
itemIndex: item.itemIndex
})
},
And finally my computed property code:
items() {
let finalArray = [];
this.selectedStop.categories.forEach(
(cat, catIndex) => {
let selected = cat.items.filter((item) => item.count > 0 );
if (selected.length > 0) {
//here we add the catIndex and itemIndex to have it calling the rigth shit
selected = selected.map(val => {
let itemIndex = cat.items.findIndex( itemToFind => itemToFind.id === val.id);
return {
...val,
catIndex: catIndex,
itemIndex: itemIndex,
}})
finalArray = finalArray.concat(selected);
}
});
return finalArray;
}
What confuses me the most is that I have almost the same code in another component, and there it's working as expected, and although the model is changed, the computed property is only recalculated on the first item,
After reading this gist and taking a look again at the posts describing this kind of issue, I decided to give it a try and just make a copy of the whole stored object not just the property, update it, then set it back on vuex using Vue.set, and that did the trick, everything is now working as expected, this is my final store method.
ADD_ITEM_TO_SELECTED_STOP(state, payload) {
let selectedLocalStop = JSON.parse(JSON.stringify(state.selectedStop));
let count = selectedLocalStop.categories[payload.catIndex].items[payload.itemIndex].count;
selectedLocalStop.categories[payload.catIndex].items[payload.itemIndex].count = count + 1;
Vue.set(state,"selectedStop", selectedLocalStop );
//Now we search for this step on the main list
const stepIndex = state.stops.findIndex(val => val.id === selectedLocalStop.id);
Vue.set(state.stops,stepIndex, selectedLocalStop );
},
I had to add the last bit after updating the whole object, because, originally, the array items were updated when the selected item was changed, I guess some sort of reference, but with the object creation, that relationship no longer works "automatic" so I need to update the array by hand
I have a function that runs every time your location changes and I'm trying to set a value in my array when a certain if statement is found true. All I seem to be doing is removing everything from my variable except the value that I am changing. Bad explanation so here is some code...
The data starts like this:
this.state = { selectedItem: [] }
And will change to something like this during normal app use:
selectedItem: [{address: 'Somewhere', latitude: -37.826835, longitude: 144.992030, found: false }]
Here is where I am trying to change the data (This will always run after some data is added):
const newSelectedItem = () => {
let copyB = {...this.state.selectedItem};
copyB.found = true;
return copyB;
};
this.setState({selectedItem: newSelectedItem});
When I try to run:
{this.state.selectedItem.address}
I see the initial value which would be the address "Somewhere" but when my function runs based on location change it disappears. What have I actually done to my data in my above function?
Have I just made it selectedItem: [{found:true}] or something dumb like that?
You were doing fine except for one single thing , in this function :
const newSelectedItem = () => {
let copyB = {...this.state.selectedItem};
copyB.found = true;
return copyB;
};
this.setState({selectedItem: newSelectedItem});
here copyB is now an object , but yours selected Item was an array. So the problem is now selectedItem is now an object when you do setState with newSelctedItem.
SO copyB.found = true; wouldnt evaluate anything rather, try copyB[0].found = true; so there the value will be accessed and return true accordingly.
And when you try to access the state , replace {this.state.selectedItem.address}
with {this.state.selectedItem[0].address} ,
Hope i helps. feel free to ask any doubts.
You can play around with this pen codepen
Well, this is pretty weird since you still get the initial value. There are some problems in your code:
You want to store your variables in a array: It's fine, but the problem comes from the way you retrieve and set your value. Since selectedItem, your {this.state.selectedItem.address} because this is an object destructuring. To do that, you have to destruct your array first, e.g: item = selectedItem[0] or using map, etc... After that, you can try: item.address.
Another problem is from your newSelectedItem. Since let copyB = {...this.state.selectedItem}; will destruct your selectedItem, take all its properties and set to newSelectedItem, it will make your selectedItem become an object, not an array anymore.
If your selectedItem stores only 1 object, so don't use array. This selectedItem will become:
selectedItem: {
address: 'Somewhere',
latitude: -37.826835,
longitude: 144.992030,
found: false
}
It looks like a JSON object, hence you can do: selectedItem.address
In case you still don't get it, place a little debug or a console.log("selectedItem", this.state.selectedItem) to see what happend, and you will find out.
I think in the end I was mapping a function to the data and not the data itself. This was my eventual solution...
const newMyWaypoinys = this.state.myWaypoints.map(a => {
let copyA = {...a};
if (copyA.address === wp.address) {
if (copyA.address === this.state.selectedItem.address) {
this.setState(prevState => ({
selectedItem: {
...prevState.selectedItem,
found: true
}
}))
}
copyA.found = true;
}
return copyA;
});
this.setState({
myWaypoints: newMyWaypoinys,
});