getters not reactive in vuex - 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(...)
};

Related

Vue 3 - watch when the value is changed in the watch

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?

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;
}
},

To-dos don't update when I choose a to-do list

I have two components: TodoList and TodoListsList. They get their data from states in todos.js and todoLists.js modules accordingly. When I choose some to-do list, i.e mark it as active, TodoListsList is updated, but TodoLists isn't, thought the data is updated. Here's how I do it.
todoListsState and markAsActive() (todoLists.js):
import todos from '#/modules/todos.js'
// ... some code ...
const todoListsState = reactive({
todoLists: [],
todoListsAreLoading: false,
removedTodoListId: null,
editedTodoListId: null,
editedTodoListName: '',
baseTodoListsApiUrl: process.env.VUE_APP_BASE_TODO_LISTS_API_URL,
todoListCreationFormModalId: 'todoListCreationFormModal',
todoListNameChangeFormModalId: 'todoListNameChangeFormModal'
});
// ... some code ...
function markAsActive(value) {
let { close } = infoToast();
if (value) {
axios.post((todoListsState.baseTodoListsApiUrl + 'mark-as-active'), {
activatedTodoListId: value
}).then(function () {
getTodoLists();
const { getTodos } = todos();
getTodos();
}).catch(function () {
dangerToast('Failed to mark to-do list as active.');
}).finally(() => {
close();
});
}
}
todosState and getTodos() (todos.js):
const todosState = reactive({
todos: [],
activeTodoListId: 0,
removedTodoId: null,
editedTodoId: null,
editedTodoText: '',
todosAreLoading: false,
baseTodosApiUrl: process.env.VUE_APP_BASE_TODOS_API_URL,
todoAdditionFormModalId: 'todoAdditionFormModal',
todoEditFormModalId: 'todoEditFormModal'
});
// ... some code ...
async function getTodos() {
try {
todosState.todosAreLoading = true;
const response = await axios.get(todosState.baseTodosApiUrl);
todosState.activeTodoListId = response.data[0];
todosState.todos = response.data[1];
} catch (e) {
dangerToast('To-dos loading failed.');
} finally {
todosState.todosAreLoading = false;
}
}
How does todosState.todos look in console:
todosState.todos when Todos.vue is mounted:
It doesn't look like the array looses it's reactivity.
If you need something else to understand my question, feel free to ask. Help appreciated.
The problem is solved! I have just moved todosState out of
export default function () {}
and it works! Finally! This thread helped me a lot.

Wait until API fully loads before running next function -- async/await -- will this work?

I am a beginner with Javascript with a bit of knowledge of VueJs. I have an array called tickets. I also have a data api returning two different data objects (tickets and user profiles).
The tickets have user ids and the user profiles has the ids with names.
I needed to create a method that looks at both of that data, loops through it, and assigns the full name of the user to the view.
I was having an issue where my tickets object were not finished loading and it was sometimes causing an error like firstname is undefined. So, i thought I'd try and write an async/await approach to wait until the tickets have fully loaded.
Although my code works, it just doesn't "feel right" and I am not sure how reliable it will be once the application gets larger.
Can I get another set of eyes as to confirmation that my current approach is OK? Thanks!
data() {
return {
isBusy: true,
tickets: [],
userProfiles: [],
}
},
created() {
this.getUserProfiles()
this.getTickets()
},
methods: {
getUserProfiles: function() {
ApiService.getUserProfiles().then(response => {
this.userProfiles = response.data
})
},
getTickets() {
ApiService.getTickets().then(response => {
this.tickets = response.data
this.assignNames(this.tickets)
this.isBusy = false
})
},
// lets wait until the issues are loaded before showing names;
async assignNames() {
let tickets = await this.tickets
var i
for (i = 0; i < this.tickets.length; i++) {
if (tickets[i].assigned_to !== null) {
const result = this.userProfiles.filter(profile => {
return profile.uid == tickets[i].assigned_to
})
tickets[i].assigned_to = result[0].firstname + ' ' + result[0].lastname
}
}
}
}
}
</script>
There are several ways you could do this. Here is the one I prefer without async/await:
created() {
this.load();
},
methods: {
getUserProfiles: function() {
return ApiService.getUserProfiles().then(response => {
this.userProfiles = response.data
})
},
getTickets() {
return ApiService.getTickets().then(response => {
this.tickets = response.data
})
},
load() {
Promise.all([
this.getUserProfiles(),
this.getTickets()
]).then(data => {
this.assignNames();
this.isBusy = false;
});
},
assignNames(){
const tickets = this.tickets;
for (let i = 0; i < this.tickets.length; i++) {
if (tickets[i].assigned_to !== null) {
const result = this.userProfiles.filter(profile => {
return profile.uid == tickets[i].assigned_to
})
tickets[i].assigned_to = result[0].firstname + ' ' + result[0].lastname
}
}
}
}

Vuex, State, & Getters

I've been stumped on getting my data to be reactive down to some chartjs charts.
I have a vuex store that when the app is mounted fires off an action to do some API cals (10 in total). Each API call is related to a department and it returns an array of all orders. That all works fine and I can see the data in vuex in the dev tools.
On my home page, I attempt to build 10 different charts. each chart has datasets related to a department's orders. The datasets are broken down to display late orders, on time, already shipped, etc.
So in my component that passes the data to the charts I use a state getter to fill out my data sets. So one of my getters is like this:
getDepartmentLateOrders: (state) => (department) => {
let resultArray = []
state[department].forEach(function(order, index) {
let now = new Date()
let orderShipDate = new Date(order.ExpectedShipDate)
let diff = date.getDateDiff(orderShipDate, now)
let statusCheck = outOfProductionStatuses
if(diff < 0 && !order.Invoiced && !statusCheck.includes(order.LastStatus)) {
resultArray.push(order)
}
})
return resultArray
Here is my store :
const store = new Vuex.Store({
state: {
//These are filled from setOrdersData()
dept1Orders: [],
dept2Orders: [],
dept3Orders: [],
loaded: false,
departmentsArray : ['dept1', 'dept2', 'dept3']
},
actions: {
//This is called when the app is mounted
loadOrders: function({ commit }) {
let departmentSeach = this.state.departmentsArray
departmentSeach.forEach(function(dept, index) {
api.getDeptStatus(dept.toLowerCase()).then((response) => {
store.commit('setOrdersData', { dept: dept, orders: response })
}, (err) => {
console.log(err)
})
})
this.state.loaded = true
}
},
mutations: {
//For reference: https://vuex.vuejs.org/en/mutations.html
setOrdersData: (state, payload) => {
Vue.set(state, payload.dept+'Orders', payload.orders)
},
},
getters: {
getDepartmentLateOrders: (state) => (department) => {
let resultArray = []
state[department].forEach(function(order, index) {
let now = new Date()
let orderShipDate = new Date(order.ExpectedShipDate)
let diff = date.getDateDiff(orderShipDate, now)
let statusCheck = outOfProductionStatuses
if(diff < 0 && !order.Invoiced && !statusCheck.includes(order.LastStatus)) {
resultArray.push(order)
}
})
return resultArray
},
getDepartmentShipTodayOrders: (state) => (department) => {
let resultArray = []
state[department].forEach(function(order, index) {
let now = new Date()
let orderShipDate = new Date(order.ExpectedShipDate)
let diff = date.getDateDiff(orderShipDate, now)
let statusCheck = outOfProductionStatuses
if(diff === 0 && !order.Invoiced && !statusCheck.includes(order.LastStatus)) { //TODO: Have this not exclude the out of production orders. But maybe have it highlight so it knows?
resultArray.push(order)
}
})
return resultArray
},
getDepartmentOutOfProductionOrders: (state) => (department) => {
let resultArray = []
let statusCheck = outOfProductionStatuses
state[department].forEach(function(order, index) {
if(statusCheck.includes(order.LastStatus) && !order.Invoiced) {
resultArray.push(order)
}
})
return resultArray
},
getDepartmentShippedOrders: (state) => (department) => {
let resultArray = []
//let statusCheck = ['SHIPPING-3']
state[department].forEach(function(order, index) {
if(order.Invoiced) {
resultArray.push(order)
}
})
return resultArray
},
getDepartmentOnTimeOrders: (state) => (department) => {
let resultArray = []
//let statusCheck = ['SHIPPING-3']
state[department].forEach(function(order, index) {
let now = new Date()
let orderShipDate = new Date(order.ExpectedShipDate)
let diff = date.getDateDiff(orderShipDate, now)
let statusCheck = outOfProductionStatuses
if(diff > 0 && !order.Invoiced && !statusCheck.includes(order.LastStatus)) {
resultArray.push(order)
}
})
return resultArray
},
}
})
Here is how I get to the data in the component that needs it:
fillData () {
let store = this.$store;
let departmentSeach = this.departmentArray
let that = this
departmentSeach.forEach(function(dept, index) {
console.log('get '+ dept)
that[dept+'DataCollection'] = Object.assign({}, that[dept+'DataCollection'],
{
datasets: [{
data: [
store.getters.getDepartmentShipTodayOrders(dept+'Orders').length,
store.getters.getDepartmentLateOrders(dept+'Orders').length,
store.getters.getDepartmentOutOfProductionOrders(dept+'Orders').length,
store.getters.getDepartmentShippedOrders(dept+'Orders').length,
store.getters.getDepartmentOnTimeOrders(dept+'Orders').length
//12, 13, 14, 15, 16
],
backgroundColor: that.defaultColors
}],
// These labels appear in the legend and in the tooltips when hovering different arcs
labels: that.defaultLables
})
})
this.loadingVisible = false
this.loaded = true
},
The problem is that my getters are running BEFORE all my API requests are completed. I had thought that this would be fine because it would be reactive, so as data came in and set the state data, the component that is using the getter would have data bound to it because I'm referencing the state getters that are using the state data.
On my initial charts page load, the charts don't render with the data, except the very first department (probably because the API call is fast enough). If I view a page in the app and go back to the charts page all my data is in there for the other departments and it works great.
So my question... what am I doing wrong in this example that the data is not being reactive? I'm sure it's some kind of reactivity gotcha that I am overlooking.
So the main issue that this all was related to was how I instantiated my data for the charts. I set the empty department order objects in data. However, the correct way of doing it is to use computed instead because the data is derived from the store and will be reactive to the data as it comes in to my stores and the getters respond. After I did that everything worked just fine.