VueJS 3 Prevent re-computation if computed value hasn't changed - vue.js

I have a chain of computed values like this:
var lt = reactive({
scrollTop: shallowRef(0),
row: computed(() => Math.floor(lt.scrollTop / lt.rowHeight)),
records: computed(() => props.collection.filter((x, i) => {
console.log(lt.row);
return i > row && i < row + 30;
}))
})
scrollTop is tied to a scroll listener. This setup works, but I notice that the records computed is getting recalculated even though row hasn't actually changed. I understand this happens because scrollTop changes, and triggers the proxies all the way down the chain.
Is there a way to ensure a computed doesn't change unless there's an actual value change in a direct contributor var? If not, is there a better pattern for the data here that would solve this?

Related

Add value at the beginning of an array in react-native hook

I am functional component I am using hooks to update the state of the array locationData
const c = {
title: inputValue,
}
setLocationData([...locationData, c]);
This is working fine, but now I want to add the value at the beginning of the array, and not at the end.
Edit:
I also have a problem to delete an item from the array. I want to delete one item, but more are deleted
const deleteItem = (index) => {
console.log(index)
var temp = locationData;
var temp = locationData.splice(index, 1);
setLocationData(temp);
}
You are almost there, just switch the position in the array.
setLocationData([c, ...locationData]);
You should also be aware of other methods like splice, slice, push, pop etc...
Update: Using Splice
This relates to part 2 of your question with regards to removing from specific index.
The splice() method changes the contents of an array by removing or
replacing existing elements and/or adding new elements in place. To
access part of an array without modifying it, see slice().
So your codes should be similar to the following
var temp = [...locationData];
temp.splice(index, 1);
setLocationData(temp);

Vuex model update won't reload computed property

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

VueJS: Computed Property inside a loop

My scenario is that of the picture:
I have some transaction headers and transaction details. The screenshot is a pop-up dialog for editing a transaction...
One transaction could be a member ship fee. If a member pays a fee (see 1) then I want to be able to enter the month related to the fee.
Each "Buchungsvorgang" (transaction detail) is being looped through with v-for:
<v-row
v-for="(item, index) in editedItem.transactionDetail"
:key="index"
dense
align="center"
class="mb-2"
>
I also want to show the months for which a member has already paid previously.
I have set it by:
When the name (see figure 1) is changed call a method:
async showMonths (idPerson) {
try {
const response = await this.$axios.$get(`/api/api.php/records/transactions?filter=idPerson,eq,${idPerson}&size=5`)
this.lastMonths = response.records
.map((item) => {
return `${this.$moment(item.month, 'YYYY-MM').format('MMM YY')} - (${new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(item.Amount)})`
})
.join(' // ')
} catch (e) {
this.lastMonths = e.message
}
}
This works perfectly. It's an async function as it always picks up the latest info directly from the database.
So each time, if a change the member (see number 1), the output changes.
My problem: It only changes when someone triggers the change event of the form. If I open the dialog, number two would be empty because no one triggered the event in the first place.
This is the way it looks when I open the dialog.
Question:
Can I use a async computed property here and as a parameter, pass the editedItem.transactionId to the prop in order to retrieve the data?
Or can I put the method inside the data () - function? I want the output to be visible all the time, not just if someone clicks on a field.
I have created to small codepen to illustrate the problem:
https://codepen.io/rasenkantenstein/pen/qBdZepM
The first form (person.name) is meaningless. However, the city is the variable equal to figure 1. The result should be printed as the :message property of figure 2 (city).
How - when loading the codepen - can I populate both details?
I've updated your codepen that does what I think you want, see example.
Essentially, you just set your messages on either created or mounted hooks:
created: function () {
this.people.forEach((item, i) => {
Vue.set(this.message, i, item.country)
})
}
The key thing to note above is the use of Vue.set, since Vue cannot detect changes to an array when you directly set an item with the index, see the documentation around this. So I recommend you use Vue.set inside your changeCity function as well.

My Vuex getter is not getting automatically updated.. how I can debug it?

I'm using Vuex with a getter that filters a lot of data and then some components present it to the user grouped by status. The user can increment the visible count of elements per status by 5. How many items are visible currently is on the Vuex store and a getter uses this to create a "View object".
When I update this visibility object the getter is no rerun so something in the dependency tracking went south. I'm not adding or deleteing properties, still I'm using Vue.set(...) just to be sure.
This is the mutation that increments the visible amount of items for a status:
viewMore(state, status) {
console.log('viewMore')
const current = state.visibility.statuses[status]
Vue.set(state.visibility.statuses, status, current + 5)
}
This mutation is running well and I can see in the developer tools how the visibility increments reactively with every commit. Now here is the getter that depends on this data:
visibleProspects(state, getters) {
console.log('visibleProspects')
let result = {}
for (const status in getters.sourceData) {
if (!result[status]) {
result[status] = { prospects: [] }
}
getters.sourceData[status].forEach(function(prospect) {
if (result[status].prospects.length < state.visibility.statuses[status])
result[status].prospects.push(prospect)
})
}
return result
}
What this does is traverses a complex getter named sourceData (not shown here for brevity) and then depending on how many visible items there are it returns a new structure with that maximum in an array. visibleProspects is then used by my components and everything runs fine the first time or if a update the data that sourceData computes (e.g adding / editing / deleting a prospect).. but no matter what I do modifying state.visibility.statuses is not forcing visibleProspects to recompute.
How can I debug this?
You can make deep copy to make it reactive (using JSON.parse(JSON.stringify())
viewMore(state, status) {
console.log('viewMore')
const current = state.visibility.statuses[status]
state.visibility.statuses[status] = current + 5
state.visibility = JSON.parse(JSON.stringify(state.visibility))
}
#ittus 's answer should work. But the clone operation would be heavy if your state is big.
alternatively, you may try using Vue.set on the root state state.visibility instead. This should make the reactivity works as expected.
Vue.set(state.visibility, 'statuses', {
...state.visibility.statuses,
[status]: current + 5
})

Change styling of dojo treegrid cell if value changes

What I have is a treegrid populated with values from ajax. Every 30 seconds the store is refreshed and new data is displayed.
I need to change the styling (color or background-color) of a treegrid cell when it's value differs from the old one. The requirement is to make the comparison and styling from javascript.
Any ideas on how this could be done ?
You could use dijit.Tree's getRowStyle method to modify the style dynamically. It will be called whenever a row needs to be rendered.
Something like this might get you started:
(function(){ // closure for private variables
var previousValues = {};
var myTree = ... // lookup dijit.Tree via dijit.byId, or create
myTree.getRowStyle = function(item){
var style = {};
var itemId = myTree.store.getIdentity(item);
var newValue = myTree.store.getValue(item, "MY_ITEM_VALUE");
if(newValue !== null &&
previousValues[itemId] !== null &&
previousValues[itemId] !== newValue) {
style.backgroundColor = "#0000FF";
previousValues[itemId] = newValue;
}
return style;
};
})();
There may be better ways to keep track of the previous values, but since your store is getting changed, I really can't think of one.