Vue 3 - watch when the value is changed in the watch - vue.js

I have this watcher, where I am changing value of the activeTable - this value is stored inside Pinia store:
const { tables, freeTables, usedTables, activeTable, selectedTable } = storeToRefs(useTableStore())
watch([tables, urlPath], (newValue, oldValue) =>
{
if(urlPath.value[0] === 'tables' && Number.isInteger(+urlPath.value[1]))
{
let tableIndex = tables.value.findIndex(table =>
+table.id === +urlPath.value[1]
)
if(tableIndex > -1)
{
let table = tables.value[tableIndex]
if(+table.userID === +userData.id || +table.waiterID === +userData.id)
{
mineButton.click();
}
else
{
document.getElementById('other-tables').click()
}
activeTable.value = table
}
}
})
In another component I am watching activeTable
const {showTableOrder, activeTable, selectedTable} = storeToRefs(useTableStore())
watch(
() => activeTable.value,
async (newValue, oldValue) => {
console.log(newValue);
console.log(oldValue);
},
{ deep: true }
)
If you refresh the page, the first watcher is working as expected, and activeTable is set properly, but the second watcher is never activated. What am I doing wrong here?

Related

How to pass a computed as a prop to a component?

I have 1 component to which I pass a computed as a prop in this way:
<Datatable :extraParams="extraParams" />
the computed is in the attached image.
I'm having trouble with the value of this property: coverageSelected:coverageData
Coverage data is filled by a select multiple
The problem I have is that when selecting an element of the select, first the component function is executed, then the coverageSelected property is empty, then the computed is executed and until this moment the coverageSelected array is filled, then until the second attempt It already has a full array.
This is my computed
props: [
"status_selected",
"rows",
"totals",
"dateRangeValue",
"coverageSelected",
"coverageList",
"showAll",
"dateFilterSelected",
],
computed(){
extraParams() {
let coverageData = this.coverageList.filter((m) => this.coverageSelected.includes(m.variable));
return {
status: this.status_selected,
dateRange: this.dateRangeValue,
dateFilterSelected: this.dateFilterSelected,
coverageSelected: coverageData, //This is the property that is not late.
showAll: this.showAll,
};
},
}
Another detail to mention that this.coverageSelected is a prop
The method that is executed first in the computed is this:
async getList(params) {
this.loading = true;
try {
if (params) {
this.query = { ...this.query, ...params, ...this.extraParams, filters: this.filters };
} else {
this.query = { ...this.query, ...this.extraParams, filters: this.filters };
}
const { data } = await this.$axios.post(`${this.$config.routePrefix}${this.action}`, this.query);
if (data.code == 200) {
this.rows = data.rows;
this.total = data.total;
this.$emit("listed", data);
}
} finally {
this.loading = false;
}
},

Vue react to Setting array of an Object to another array and seeing reactive changes

I have a v-data-table on vue, which gets data and dynamically adds and deltes rows based on the incoming object of arrays, Vue is reactive to adding and deleting but doesn't seem to react to array replace.
My function to add, delete and replace is the setup the following way:
function update_helper(update_obj, dataObject, colObject) {
update_obj.Data.forEach((item) => {
if (typeof item.RowData !== 'undefined'){
let temp_list = updateRow(item, colObject);
temp_list.forEach((row_obj) => {
var found = dataObject.find(Element => Element.RowID === row_obj.RowID);
if (typeof found !== 'undefined'){
//Replace
var found = dataObject.findIndex(Element => Element.RowID === item.RowID);
//console.log(row_obj);
//console.log(dataObject[found]);
dataObject[found] = row_obj;
}
else{
// Add
dataObject.push(row_obj);
}
});
}
else if (typeof item.RowData === 'undefined') {
// Delete
var found = dataObject.findIndex(Element => Element.RowID === item.RowID);
dataObject = dataObject.splice(found, 1);
}
});
}
The function keeps track of the row Id . My replace function dataObject[found] = rowObj works but isn't reactive, i.e the change can only be seen when I switch tabs or refresh the page.
How do I workaround this.
Instead of passing it as argument, you could better have it as a data variable like
data() {
return {
dataObject: [],
}
}
and then define your function inside the methods section like
methods: {
update_helper(update_obj, colObject) {
update_obj.Data.forEach((item) => {
if (typeof item.RowData !== 'undefined'){
let temp_list = updateRow(item, colObject);
temp_list.forEach((row_obj) => {
var found = dataObject.findIndex(Element => Element.RowID === row_obj.RowID);
if (found !== -1){
this.dataObject[found] = row_obj;
}
else{
// Add
this.dataObject.push(row_obj);
}
});
}
else if (typeof item.RowData === 'undefined') {
// Delete
var found = this.dataObject.findIndex(Element => Element.RowID === item.RowID);
dataObject = this.dataObject.splice(found, 1);
}
});
}
}
If possible you can declare the colObject also in the data() section
Note: If you observe the above function body, I would have accessed the dataObject using this operator.

getters not reactive in vuex

I have following store defined:
state: () => ({
infoPackCreationData: null,
infoPackCreationTab: null,
}),
getters: {
infoPackImage(state: any) {
return state.infoPackCreationTab && state.infoPackCreationTab.infopackContents
? state.infoPackCreationTab.infopackContents.filter((item: any) => item.type === "IMAGE")
: [];
}
},
mutations: {
setImageData(state:any, infopackImageData: any) {
state.infoPackCreationTab.infopackContents.filter((item: any) => {if(item.type === "IMAGE")
item = infopackImageData
console.log(item , 'this is items');
return item})
}
},
actions: {
setImageData(context: any, payload: any) {
context.commit('setImageData', payload)
}
}
and in my component I am using the computed to get the imageList:
computed: {
...mapGetters("creationStore", ["infoPackImage"]),
imageList: {
get() {
return this.infoPackImage ?? [];
},
set(value) {
this.$store.dispatch('creationStore/setImageData', value);
}
}
},
The problem is I want to edit a value of the imageList by index using draggable libarary,
but imageList does not act reactive and it just move the image and not showing the other image in the previous index:
async imageChange(e) {
this.loading = true
let newIndex = e.moved.newIndex;
let prevOrder = this.imageList[newIndex - 1]?.order ?? 0
let nextOrder = this.imageList[newIndex + 1]?.order ?? 0
const changeImageOrder = new InfopackImageService();
try {
return await changeImageOrder.putImageApi(this.$route.params.infopackId,
this.$route.params.tabId,
e.moved.element.id, {
title: e.moved.element.title,
infopackAssetRef: e.moved.element.infopackAssetRef,
order: nextOrder,
previousOrder: prevOrder,
}).then((res) => {
let image = {}
let infopackAsset = e.moved.element.infopackAsset
image = {...res, infopackAsset};
Vue.set(this.imageList, newIndex , image)
this.loading = false
return this.imageList
});
} catch (e) {
console.log(e, 'this is put error for tab change')
}
},
Array.prototype.filter doesn't modify an array in-place, it returns a new array. So this mutation isn't ever changing any state:
mutations: {
setImageData(state:any, infopackImageData: any) {
state.infoPackCreationTab.infopackContents.filter((item: any) => {if(item.type === "IMAGE")
item = infopackImageData
console.log(item , 'this is items');
return item})
}
},
So, if you intend to change state.infoPackCreationTab.infopackContents, you'll need to assign the result of filter():
mutations: {
setImageData(state:any, infopackImageData: any) {
state.infoPackCreationTab.infopackContents = state.infoPackCreationTab.infopackContents.filter(...)
However, since state.infoPackCreationTab did not have an infopackContents property during initialization, it will not be reactive unless you use Vue.set() or just replace the whole infoPackCreationTab object with a new one (see: Vuex on reactive mutations):
mutations: {
setImageData(state:any, infopackImageData: any) {
state.infoPackCreationTab = {
...state.infoPackCreationTab,
infopackContents: state.infoPackCreationTab.infopackContents.filter(...)
};

Is there a way to do pagination with firebase realtime database (vuejs)?

I'm trying to paginate my data from firebase realtime database.
Do I have to change to firestore ? Where all is explain in Google's doc (https://firebase.google.com/docs/firestore/query-data/query-cursors) or it's also possible with rtdb ?
Here is my code (i'm using vue js) :
loadConcerts ({commit}) {
commit('setLoading', true)
firebase.database().ref('concerts')
.orderByChild('expires')
.startAt(Date.now() / 1e3)
.limitToFirst(10)
.once('value')
.then(data => {
const concerts = []
data.forEach(element => {
concerts.push({
id: element.key,
title: element.val().title,
day: element.val().day,
ticketlink: element.val().ticketlink,
description: element.val().descriptio
})
})
commit('setLoadedConcerts', concerts)
commit('setLoading', false)
})
.catch(
(error) => {
console.log(error)
commit('setLoading', false)
}
)
},
I would like to add pagination after 10 results, or infinite scrolling.
I have also had similar problem with pagination. The documentation seems to be insufficient i.e they show you how to go to next page but not how to move back to the previous page. Its just frustrating really.
I am using firestore
Below is how i implemented a simple pagination. I have already configured VueFire , Firebase and BootstrapVue i'll head straight to the code.
What to do different that no one shows you.
Use VueFire programmatic binding instead of declarative binding see here
To get firstVisible item in firebase run documentSnapshots.docs[0]
<template>
<div>
<p>{{countries}}</p>
<b-button-group size="lg" class="mx-2">
<b-button :disabled="prev_btn" #click="previous" >«</b-button>
<b-button :disabled="next_btn" #click="next">»</b-button>
</b-button-group>
</div>
</template>
<script>
import firebase from 'firebase/app'
import 'firebase/auth'
import { db } from '../main'
export default {
name: 'Countries',
data () {
return {
countries: [],
limit: 2,
lastVisible: '',
firstVisible: '',
next_btn: false,
prev_btn: true
}
},
methods: {
next () {
if (!this.next_btn) {
// bind data with countries
this.$bind('countries', db.collection('Countries').orderBy('createdAt').startAfter(this.lastVisible).limit(this.limit))
// set last and first visible items
db.collection('Countries').orderBy('createdAt').startAfter(this.lastVisible).limit(this.limit).get().then(documentSnapshots => {
this.lastVisible = documentSnapshots.docs[documentSnapshots.docs.length - 1]
this.firstVisible = documentSnapshots.docs[0]
}).then(() => {
// Peep on the next next query to see if it gives zero
db.collection('Countries').orderBy('createdAt').startAfter(this.lastVisible).limit(this.limit).get()
.then(snap => {
if (snap.size === 0) {
//disable button if the next peeped result gets zero
this.next_btn = true
// enable previous button
this.prev_btn = false
} else {
// enable next button if peeped result is not zero
this.next_btn = false
// enable previous button
this.prev_btn = false
}
})
})
}
},
previous () {
// Ensure previous is not zero
db.collection('Countries').orderBy('createdAt').endBefore(this.firstVisible).limitToLast(this.limit).get().then(snap => { return snap.size })
.then(size => {
//confirm is not zero here
if (size !== 0) {
//bind the previous to countries
this.$bind('countries', db.collection('Countries').orderBy('createdAt').endBefore(this.firstVisible).limitToLast(this.limit))
// Set last and first visible
db.collection('Countries').orderBy('createdAt').endBefore(this.firstVisible).limitToLast(this.limit).get().then(documentSnapshots => {
this.lastVisible = documentSnapshots.docs[documentSnapshots.docs.length - 1]
this.firstVisible = documentSnapshots.docs[0]
}).then(() => {
// peep the next previous query
db.collection('Countries').orderBy('createdAt').endBefore(this.firstVisible).limitToLast(this.limit).get()
.then(snap => {
if (snap.size === 0) {
//if next peeped previous button gets 0 disable
this.prev_btn = true
this.next_btn = false
} else {
//if next peeped result is does not get 0 enable buttons
this.prev_btn = false
this.next_btn = false
}
})
})
}
})
}
},
mounted () {
// run first query and bind data
this.$bind('countries', db.collection('Countries').orderBy('createdAt').limit(this.limit))
// set last and first Visible
db.collection('Countries').orderBy('createdAt').limit(this.limit).get().then(documentSnapshots => {
this.lastVisible = documentSnapshots.docs[documentSnapshots.docs.length - 1]
this.firstVisible = documentSnapshots.docs[0]
}).then(() => {
// peep to check if next should be on or off
db.collection('Countries').orderBy('createdAt').startAfter(this.lastVisible).limit(this.limit).get()
.then(snap => {
if (snap.size === 0) {
this.next_btn = true
}
})
})
}
}
</script>

How to trigger a change, when adding new property in object in VueJs?

I'm having the following initial structure of vuex state (in VueJS project):
state: {
level1prop: null
}
And then I'm changing it dynamically and I'm mutating it into the following structure:
state: {
level1prop: {
level2prop: {
level3prop: {
"customKey1": { /* this is some object 1 */ },
"customKey2": { /* this is some object 2 */ },
...
}
}
}
}
I will be then adding "customKeyN": { /* this is some object N */ } under the level3prop and what it's important for me is on every change to trigger a watcher, which is watching for a changes into the level1prop from the state.
Initially, in my mutation I was doing this update on the following way:
if (!state.hasOwnProperty("level1prop"))
state["level1prop"] = {};
else if (state["level1prop"] === null || state["level1prop"] === undefined)
state["level1prop"] = {};
if (!state["level1prop"].hasOwnProperty("level2prop"))
state["level1prop"]["level2prop"] = {};
else if (state["level1prop"]["level2prop"] === null || state["level1prop"]["level2prop"] === undefined)
state["level1prop"]["level2prop"] = {};
if (!state["level1prop"]["level2prop"].hasOwnProperty("level3prop"))
state["level1prop"]["level2prop"]["level3prop"] = {};
else if (state["level1prop"]["level2prop"]["level3prop"] === null || state["level1prop"]["level2prop"]["level3prop"] === undefined)
state["level1prop"]["level2prop"]["level3prop"] = {};
let payloadObj = { "customKey1": { /* this is some object 1 */ } };
state["level1prop"]["level2prop"]["level3prop"] = payloadObj;
And this is creating the structure on the way I want, but the watcher of changes is not triggered. Following the advises from here i refactor my code to a few different ways, but none of them is triggering the changes. Here is an example for latest option which I tried:
if (!state.hasOwnProperty("level1prop"))
state = Object.assign(state, { "level1prop" : {} });
else if (state["level1prop"] === null || state["level1prop"] === undefined)
state = Object.assign(state, { "level1prop" : {} });
if (!state["level1prop"].hasOwnProperty("level2prop"))
state["level1prop"] = Object.assign(state["level1prop"], { "level2prop" : {} });
else if (state["level1prop"]["level2prop"] === null || state["level1prop"]["level2prop"] === undefined)
state["level1prop"] = Object.assign(state["level1prop"], { "level2prop" : {} });
if (!state["level1prop"]["level2prop"].hasOwnProperty("level3prop"))
state["level1prop"]["level2prop"] = Object.assign(state["level1prop"]["level2prop"], { "level3prop" : {} });
else if (state["level1prop"]["level2prop"]["level3prop"] === null || state["level1prop"]["level2prop"]["level3prop"] === undefined)
state["level1prop"]["level2prop"] = Object.assign(state["level1prop"]["level2prop"], { "level3prop" : {} });
let payloadObj = { "customKey 1": { /* this is some object 1 */ } };
state["level1prop"]["level2prop"]["level3prop"] = Object.assign(state["level1prop"]["level2prop"]["level3prop"], payloadObj);
Again, this is creating the structure which I need, but watcher is still not triggered. A few other options that I tried, but were not triggering the change were:
...
state["level1prop"]["level2prop"]["level3prop"] = Object.assign({}, state["level1prop"]["level2prop"]["level3prop"], payloadObj);
...
and
...
Object.assign(state["level1prop"]["level2prop"]["level3prop"], payloadObj);
...
Is there any way to be able to trigger the watcher for a changes, in such a complex object state with so many nested levels?
As explained in the docs Object Change Detection Caveats section, you should better use the specifically designed Vue setter Vue.set to later add sub-levels to your state.
Then make sure your watcher specifies the deep option, so that it is correctly triggered when your sub-levels change.
const store = new Vuex.Store({
state: {
level1prop: null,
},
});
const state = store.state;
if (!state["level1prop"])
Vue.set(state, "level1prop", {})
if (!state["level1prop"]["level2prop"])
Vue.set(state["level1prop"], "level2prop", {})
if (!state["level1prop"]["level2prop"]["level3prop"])
Vue.set(state["level1prop"]["level2prop"], "level3prop", {})
let payloadObj = {
"customKey1": {
hello: "world",
},
};
state["level1prop"]["level2prop"]["level3prop"] = payloadObj;
setTimeout(() => {
// Change an already existing key.
state["level1prop"]["level2prop"]["level3prop"].customKey1.hello = "too";
}, 1000);
setTimeout(() => {
// To add or remove keys, make sure to use again Vue.set or Vue.delete.
state["level1prop"]["level2prop"]["level3prop"].customKey1.hello = "too";
Vue.set(state["level1prop"]["level2prop"], "level3propSibling", {
hi: "again",
});
}, 2000);
new Vue({
store: store,
watch: {
"$store.state": {
// Make sure you specify the `deep` option
deep: true,
handler() {
console.log(store.state);
},
},
},
});
<script src="https://unpkg.com/vue#2"></script>
<script src="https://unpkg.com/vuex#3"></script>