VueX and axios posting previous data along with data from new request - vuejs2

I have a postAsset action in my vuex store like so
async postAsset({dispatch}, asset) {
const f = await dispatch('srcToFile', asset);
asset[0].files.fileList = f;
const fileData = asset[0].files.fileList;
const detailData = asset[0].detail;
const fData = new FormData();
fData.append('Name', asset[0].name);
Object.keys(detailData).forEach((key) => {
fData.append(`Detail.${key}`, detailData[key]);
});
for (var i = 0; i < fileData.length; i++) {
fData.append('Files', fileData[i]);
}
await axios({
method: 'post',
url: 'https://localhost:5001/api/Assets',
data: fData,
headers: {
'Content-Type': undefined
}
})
.then(function(response) {
console.warn(response);
})
.catch(function(response) {
console.warn(response);
});
}
It is successfully posting to my api backend and to the database.
The issue that I am running into is that after I make the first post it posts the previous data and the new data I do not know why it is doing this. I did add await to the axios call but that just slowed it down it is still posting two times after the first and im sure if i keep posting it will continue to post the previous ones into the db again and again. Im at a loss as to what is going on so reaching out for some assistance to see if I can get this resolved.
examples of what it looks like in the db
does anyone have any advice for me so I can get this fixed? I should only be getting one item posted at a time that is the desired result. I have gone through my inputs and put in .prevent to stop them from clicking twice but I don't think it is that .. this is like it is saving the data and reposting it all at once each time I add a new record .
UPDATE:
the code that calls the action
populateAssets ({ dispatch }, asset) {
return new Promise((resolve) => {
assets.forEach((asset) => {
commit('createAsset', asset);
);
dispatch('postAsset', asset);
resolve(true);
});
},
the populate assets populates a list with a completed asset.
and asset is coming from the srcToFile method
that converts the files to a blob that I can post with
async srcToFile(context, asset) {
const files = asset[0].files.fileList;
let pmsArray = [];
for (let f = 0; f < files.length; f++) {
var data = files[f].data;
let name = files[f].name;
let mimeType = files[f].type;
await fetch(data)
.then(function(res) {
const r = res.arrayBuffer();
console.warn('resource ', r);
return r;
})
.then(function(buf) {
console.warn('buffer: ', [buf]);
let file = new File([buf], name, { type: mimeType });
pmsArray.push(file);
});
}
console.warn(pmsArray);
return pmsArray;
},
asset is an array from my add asset component
structure of asset
name: '',
detail: {
category: '',
manufacturer: '',
model: '',
serialNumber: '',
purchasePlace: '',
quantity: 1,
acquiredDate: '',
purchasePrice: '',
currentValue: '',
condition: '',
assetLocation: '',
retiredDate: '',
description: ''
},
files: {
fileList: []
}
hope this helps out some
the whole store file
import Vue from 'vue'
import Vuex from 'vuex'
import { states } from '../components/enums/enums'
import { getField, updateField } from 'vuex-map-fields'
import axios from 'axios'
Vue.use(Vuex);
const inventory = {
namespaced: true,
strict: true,
state: {
assets: {
items: []
},
categories: [],
manufacturers: [],
assetLocations: [],
conditions: ['New', 'Fair', 'Good', 'Poor']
},
getters: {
assetItems: state => state.assets.items,
getAssetById: (state) => (id) => {
return state.assets.items.find(i => i.id === id);
},
conditions: (state) => state.conditions,
categories: (state) => state.categories,
manufacturers: (state) => state.manufacturers,
assetLocations: (state) => state.assetLocations
},
mutations: {
createAsset (state, assets) {
state.assets.items.push(assets);
},
createCategories (state, category) {
state.categories.push(category);
},
createManufacturers (state, manufacturer) {
state.manufacturers.push(manufacturer);
},
createLocations (state, locations) {
state.assetLocations.push(locations);
}
},
actions: {
addToCategories ({ commit }, categories) {
commit('createCategories', categories);
},
addToManufacturers ({ commit }, manufacturers) {
commit('createManufacturers', manufacturers);
},
addToLocations ({ commit }, locations) {
commit('createLocations', locations);
},
populateAssets ({ dispatch }, asset) {
//return new Promise((resolve) => {
// assets.forEach((asset) => {
// commit('createAsset', asset);
// });
dispatch('postAsset', asset);
// resolve(true);
//});
},
addAsset ({ dispatch, /*getters*/ }, newAsset) {
//let assetCount = getters.assetItems.length;
//newAsset.id = assetCount === 0
// ? 1
// : assetCount++;
dispatch('populateAssets', [newAsset]);
},
async srcToFile(context, asset) {
const files = asset[0].files.fileList;
let pmsArray = [];
for (let f = 0; f < files.length; f++) {
var data = files[f].data;
let name = files[f].name;
let mimeType = files[f].type;
await fetch(data)
.then(function(res) {
const r = res.arrayBuffer();
console.warn('resource ', r);
return r;
})
.then(function(buf) {
console.warn('buffer: ', [buf]);
let file = new File([buf], name, { type: mimeType });
pmsArray.push(file);
});
}
console.warn(pmsArray);
return pmsArray;
},
async postAsset({ dispatch }, asset) {
const f = await dispatch('srcToFile', asset);
asset[0].files.fileList = f;
const fileData = asset[0].files.fileList;
const detailData = asset[0].detail;
const fData = new FormData();
fData.append('Name', asset[0].name);
Object.keys(detailData).forEach((key) => {
fData.append(`Detail.${key}`, detailData[key]);
});
for (var i = 0; i < fileData.length; i++) {
fData.append('Files', fileData[i]);
}
await axios({
method: 'post',
url: 'https://localhost:5001/api/Assets',
data: fData,
headers: { 'Content-Type': undefined }
})
.then(function(response) {
console.warn(response);
})
.catch(function(response) {
console.warn(response);
});
}
}
};
const maintenance = {
state: {
backup: []
},
strict: true,
getters: {},
mutations: {},
actions: {}
};
const assetProcessing = {
namespaced: true,
state: {
currentAsset: {
id: 0,
name: '',
detail: {
category: '',
manufacturer: '',
model: '',
serialNumber: '',
purchasePlace: '',
quantity: 1,
acquiredDate: '',
purchasePrice: '',
currentValue: '',
condition: '',
assetLocation: '',
retiredDate: '',
description: ''
},
files: {
fileList: []
}
},
filePosition: -1,
selectedItem: -1,
state: states.view,
isNewAsset: false
},
getters: {
getField,
getOpenAsset (state) {
return state.currentAsset
},
getSelectedAsset: (state, getters, rootState, rootGetters) => (id) => {
if (state.isNewAsset) return state.currentAsset
Object.assign(state.currentAsset, JSON.parse(JSON.stringify(rootGetters['inventory/getAssetById'](!id ? 0 : id))));
return state.currentAsset
},
appState: (state) => state.state,
getCurrentPosition (state) {
return state.filePosition
},
selectedAssetId: (state) => state.selectedItem
},
mutations: {
updateField,
setAsset (state, asset) {
Object.assign(state.currentAsset, asset)
},
setFiles (state, files) {
Object.assign(state.currentAsset.files, files)
},
newAsset (state) {
Object.assign(state.isNewAsset, true)
Object.assign(state.currentAsset, {
id: 0,
name: '',
detail: {
category: '',
manufacturer: '',
model: '',
serialNumber: '',
purchasePlace: '',
quantity: 1,
acquiredDate: '',
purchasePrice: '',
currentValue: '',
condition: '',
assetLocation: '',
retiredDate: '',
description: ''
},
files: {
fileList: []
}
})
},
updateSelectedItem (state, id) {
Vue.set(state, 'selectedItem', id);
},
updateState (state, newState) {
Vue.set(state, 'state', newState);
}
},
actions: {}
};
export const store = new Vuex.Store({
modules: {
inventory: inventory,
maintenance: maintenance,
assetProcessing
}
})
add asset is called when the user clicks the save button on the form
addAsset () {
this.$store.dispatch('inventory/addAsset', this.newAsset) <--- this calls add asset
this.$store.commit('assetProcessing/updateState', states.view);<-- this closes the window
},

So after much debugging we found that the eventbus was firing multiple times causing the excessive posting we added
beforeDestroy() {
eventBus.$off('passAssetToBeSaved');
eventBus.$off('updateAddActionBar');
},
to the AssetAdd.vue component and it eliminated the excessive posting of the asset.
I want to thank #phil for helping me out in this.

Related

About Vue. How to encapsulate Axios to reduce request code redundancy

Now I'm writing a project using Vue.I used a lot of Axios requests,
How to encapsulate the request code to reduce redundancy.
getProvinces() {
this.axios
.get(this.gv.serverUrl + "/location/province/list")
.then((res) => {
this.location.province.provinces = res.data.data;
});
},
getCities() {
this.axios
.get(this.gv.serverUrl + "/location/city/list", {
params: {
pid: this.location.province.province,
},
})
.then((res) => {
this.location.city.cities = res.data.data;
});
},
getCountries() {
this.axios
.get(this.gv.serverUrl + "/location/country/list", {
params: {
cid: this.location.city.city,
},
})
.then((res) => {
this.location.country.countries = res.data.data;
});
},
Use Axios.all to do concurrent requests. That will help you to encapsulate status of all requests.
Not exactly but something like this given below:
let endpoints = [
'https://this.gv.serverUrl + "/location/province/list"',
'https://this.gv.serverUrl + "/location/city/list"',
'https://api.github.com/users/ejirocodes/followers',
'https://api.github.com/users/ejirocodes/following'
];
axios.all(endpoints.map((endpoint) => axios.get(endpoint))).then(
(data) => console.log(data),
)
Here is the link for more help and good explanation: https://blog.logrocket.com/using-axios-all-make-concurrent-requests/
You could create a method that makes the axios call, passing the path and the params (as an optional argument). So a method that could work for the code you are providing could be:
fetch(resource, params) {
return this.axios.get(this.gv.serverUrl + `/location/${resource}/list`, { params })
This fetch method would return a promise, and your methods would look like this:
getProvinces() {
this.fetch("/location/province/list")
.then((res) => {
this.location.province.provinces = res.data.data;
});
},
getCities() {
this.fetch("/location/city/list", { pid: this.location.province.province })
.then((res) => {
this.location.city.cities = res.data.data;
});
},
getCountries() {
this.fetch("/location/country/list", { cid: this.location.city.city})
.then((res) => {
this.location.country.countries = res.data.data;
});
},
A further refactor could be conducted if there was some kind of uniformity in the data property of the Vue instance.
I cannot see the shape of your data property, but from what I see it looks something like this:
data() {
return {
location: {
province: {
provinces: [...],
},
city: {
cities: [...],
},
country: {
countries: [...],
},
}
}
}
If you could change it to something like this:
data() {
return {
location: {
province: {
list: [...],
},
city: {
list: [...],
},
country: {
list: [...],
},
}
}
Then the refactor of the methods could be this:
fetch(resource, params) {
return this.axios
.get(this.gv.serverUrl + `/location/${resource}/list`, { params })
.then((res) => {
this.location[resource].list = res.data.data;
})
getProvinces() {
this.fetch("/location/province/list")
},
getCities() {
this.fetch("/location/city/list", { pid: this.location.province.province })
},
getCountries() {
this.fetch("/location/country/list", { cid: this.location.city.city})
},

Apexchats.js axios gives me undefined with Vue

I am trying to get data from server using vue and apexcharts, but even after I called data with axios, it gives me undefined..
What have I missed?
template
<apexchart
ref="chart1"
width="100%"
:options="chartOptions" :series="series">
</apexchart>
data from url
{
"pageviews": 1313,
"new_users": 1014
}
script
export default {
data: function () {
return {
series: [],
chartOptions: {
chart: {
type: 'donut',
},
colors: ['#01cd49', '#007568'],
labels: ['new', 're'],
}
},
created: function () {
this.getByVisitor()
},
methods: {
getByVisitor() {
const url = 'url';
axios
.get(url)
.then(response => {
this.$refs.chart1.updateSeries([{
name: 'Sales',
data: response.data
}])
})
.catch(error => (this.byVisitor = error.data));
console.log(`---------------this.$refs.chart1`, this.$refs.chart1);
},
}
See Updating Vue Chart Data
There's no need to directly call the updateSeries() method on the chart component since it is able to react to changes in series. All you have to do is update your series data property
export default {
data: () => ({
series: [], // ๐Ÿ‘ˆ start with an empty array here
byVisitor: null, // ๐Ÿ‘ˆ you seem to have missed this one for your error data
chartOptions: {
chart: {
type: 'donut',
},
colors: ['#01cd49', '#007568'],
labels: ['new', 're'],
}
}),
created: function() {
this.getByVisitor()
},
methods: {
async getByVisitor() {
const url = 'url';
try {
const { data } = await axios.get(url)
// now update "series"
this.series = [{
name: "Sales",
data
}]
} catch (error) {
this.byVisitor = error.data
}
},
}
}

Getter undefined vuex and axios

Iโ€™m trying to get a getter in my component but it says an error. This is my code store.js
const store = new Vuex.Store({
state: {
config:{
themes: [],
typographies:[],
},
user: {
typography_id: 1,
theme_id: null
}
},
mutations: {
FETCH_CONFIG(state, config) {
state.config.themes = config.themes;
state.config.typographies = config.typographies;
},
FETCH_USER(state, user) {
state.user.theme_id = user.theme_id;
state.user.typography_id = user.typography_id;
},
},
actions: {
fetchConfig({commit}) {
axios.get('/api/config').then( function( response ){
commit('FETCH_CONFIG', response.data);
});
},
fetchUser({commit}) {
axios.get('/api/user').then( function( response ){
commit('FETCH_USER', response.data.data[0]);
});
},
},
getters: {
themes(state) {
return state.config.themes;
},
typographies(state) {
return state.config.typographies;
},
typography(state) {
if (state.user.theme_id == 1) {
return state.user.typography_id;
} else {
var theme = state.config.themes.filter(function (el) {
return el.id == state.user.theme_id;
});
return theme[0].typography_id;
}
},
user_theme(state) {
return state.user.theme_id;
},
}
});
And in my component in computed I have:
...mapGetters(['typographies', 'typography'])
And ths is the error I get:
I guess Iโ€™m doing something wrong but I donโ€™t know what.
Your getter for typography returns the error because first it goes into the else and then tries to return theme[0].typography_id - but there is an empty array.. if you are loading the date, later on, make sure that the getter returns null before data is loaded.. like:
const store = new Vuex.Store({
state: {
config:{
themes: [],
typographies:[],
},
user: {
typography_id: 1,
theme_id: null
}
},
mutations: {
FETCH_CONFIG(state, config) {
state.config.themes = config.themes;
state.config.typographies = config.typographies;
},
FETCH_USER(state, user) {
state.user.theme_id = user.theme_id;
state.user.typography_id = user.typography_id;
},
},
actions: {
fetchConfig({commit}) {
axios.get('/api/config').then( function( response ){
commit('FETCH_CONFIG', response.data);
});
},
fetchUser({commit}) {
axios.get('/api/user').then( function( response ){
commit('FETCH_USER', response.data.data[0]);
});
},
},
getters: {
themes(state) {
return state.config.themes;
},
typographies(state) {
return state.config.typographies;
},
typography(state) {
if (state.user.theme_id == 1) {
return state.user.typography_id;
} else {
var theme = state.config.themes.filter(function (el) {
return el.id == state.user.theme_id;
});
return theme.length > 0 ? theme[0].typography_id: 1;
}
},
user_theme(state) {
return state.user.theme_id;
},
}
});

Vue - Vuetify server-side datatable bug

I noticed a bug with my vuetify server-side data table. When a data table contains no data and then I attempt to add a new one, it will be successful but it is not displayed in the table, I even checked my database. After I hit the refresh button of the browser, it now shows.
But when a data already exist (even just one), the newly added data automatically reflects in the table. I don't know what is causing this behavior and it only happens when there is no data. I think it has something to do with an empty table being rendered.
Template
<v-data-table
:headers="headers"
:items="tableData"
:editItem="this.editItem"
:deleteItem="this.deleteItem"
:options.sync="pagination"
:server-items-length="totalData"
:loading="loading"
dense
>
Script
export default {
data() {
return {
editedIndex: -1,
search: "",
loading: true,
pagination: {},
dialog: false,
valid: false,
validationErrors: '',
tableData: [],
totalData: 0,
departments: [],
tableItem: {
name: '',
departmend_id: '',
},
snackbar: {
enable: false,
name: '',
message: '',
},
headers: [
{ text: 'ID', value: 'id' },
{ text: 'Department Name', value: 'name' },
{ text: 'From Department', value: 'department' },
{ text: 'Created At', value: 'created_at' },
{ text: 'Action', value: 'action' },
],
rules: {
name: [
v => !!v || 'This field is required',
v => (v && v.length <= 50) || 'Field must be less than 50 characters'
]
}
}
},
watch: {
params: {
handler() {
this.getDataFromApi().then(data => {
this.tableData = data.items
this.totalData = data.total
})
},
deep: true
}
},
computed: {
params(nv) {
return {
...this.pagination,
query: this.search
}
},
formTitle() {
return this.editedIndex === -1 ? 'New Section' : this.tableItem.name
},
},
mounted() {
this.getDataFromApi().then(data => {
this.tableData = data.items
this.totalData = data.total
})
},
created() {
this.getDepartments()
},
methods: {
async getDataFromApi() {
this.loading = true
return new Promise((resolve, reject) => {
const { sortBy, sortDesc, page, itemsPerPage } = this.pagination
let search = this.search.trim().toLowerCase()
axios.get('/api/sections').then(res => {
let items = _.orderBy(res.data, ['created_at'], ['desc'])
const total = items.length
if (search) {
items = items.filter(item => {
return Object.values(item).join(",").toLowerCase().includes(search)
})
}
if (sortBy.length === 1 && sortDesc.length === 1) {
items = items.sort((a, b) => {
const sortA = a[sortBy[0]]
const sortB = b[sortBy[0]]
if (sortDesc[0]) {
if (sortA < sortB) return 1
if (sortA > sortB) return -1
return 0
} else {
if (sortA < sortB) return -1
if (sortA > sortB) return 1
return 0
}
})
}
if (itemsPerPage > 0) {
items = items.slice((page - 1) * itemsPerPage, page * itemsPerPage)
}
setTimeout(() => {
this.loading = false
resolve({
items,
total
})
}, 300)
})
})
},
async initialize() {
this.loading = true
let res = await axios.get('/api/sections')
this.tableData = _.orderBy(res.data, ['created_at'], ['desc'])
setTimeout(() => {
this.loading = false
}, 300)
},
async getDepartments() {
let res = await axios.get('/api/departments')
this.departments = _.orderBy(res.data, ['name'], ['asc'])
},
reset() {
this.$refs.form.reset()
},
close() {
this.dialog = false
this.$refs.form.reset()
setTimeout(() => {
this.editedIndex = -1
}, 300)
},
editItem(item) {
this.editedIndex = this.tableData.indexOf(item)
this.tableItem = Object.assign({}, item)
this.dialog = true
},
updateSnackbar(e) {
this.snackbar.enable = e
},
async deleteItem(item) {
let index = this.tableData.indexOf(item)
if(confirm('Are you sure you want to delete this item?') && this.tableData.splice(index, 1)) {
let res = await axios.delete('/api/sections/' + item.id)
this.snackbar.name = ''
this.snackbar.message = res.data.message
this.snackbar.enable = true
}
},
async save() {
if (this.editedIndex > -1) {
try {
Object.assign(this.tableData[this.editedIndex], this.tableItem)
let res = await axios.put('/api/sections/' + this.tableItem.id, this.tableItem)
this.snackbar.name = res.data.name
this.snackbar.message = res.data.message
} catch(error) {
if (error.response.status == 422){
this.validationErrors = error.response.data.errors
}
}
} else {
try {
let res = await axios.post('/api/sections', this.tableItem)
this.snackbar.name = res.data.name
this.snackbar.message = res.data.message
} catch(error) {
if (error.response.status == 422){
this.validationErrors = error.response.data.errors
}
}
}
await this.initialize()
this.snackbar.enable = true
this.close()
this.reset()
},
}
}
So after countless of trials, I have finally found the solution.
try {
let res = await axios.post('/api/departments', this.tableItem)
this.getDataFromApi().then(data => {
this.tableData = data.items
this.totalData = data.total
})
}

How to add to multiple models in mongoose?

I want to update/push to multiple mongoose mode with a single payload. E.g. I have a VolSchema, a CaseSchema and a JudgeSchema. I will like to push values to the VolSchema and the JudgeSchema whenever a new Case is being created. Already it works fine when I update only the VolSchema, but when a add the JudgeSchema, I only get the Judge model updated without the Vol model.
Model/Cases.js
const mongoose = require("mongoose");
const CasesSchema = new mongoose.Schema({
title: String,
vol: { type: mongoose.Schema.Types.ObjectId, ref: "Vol", required:
true },
judges: [{ type: mongoose.Schema.Types.ObjectId, ref: "Judge" }],
});
module.exports = mongoose.model("Cases", CasesSchema)
Models/Vol.js
const mongoose = require("mongoose");
const VolSchema = new mongoose.Schema({
vol_no: {
type: String,
unique: true,
uppercase: false,
},
cases: [{ type: mongoose.Schema.Types.ObjectId, ref: "Cases" }],
createdAt: {
type: Date,
default: Date.now,
},
});
module.exports = mongoose.model("Vol", VolSchema);
Models/Judge.js
const mongoose = require("mongoose");
const JudgeSchema = new mongoose.Schema({
name: String,
bio: String,
cases: [{ type: mongoose.Schema.Types.ObjectId, ref: "Cases" }],
});
module.exports = mongoose.model("Judge", JudgeSchema);
resolvers.js
const Vol = require("../models/Vol");
const Case = require("../models/Cases");
const Judge = require("../models/Judge");
module.exports = {
Mutation: {
addCase: async (_, { title, volID, judgesID }) => {
const newCase = await new Case({
title: title,
vol: volID,
judges: judgesID,
}).save();
const newVol = Vol.updateOne(
{ _id: volID },
{ $push: { cases: newCase } },
{ new: true },
);
const judge = Judge.updateOne(
{ _id: judgesID },
{ $push: { cases: { $each: [newCase], $position: 0 } } },
{ new: true },
);
return newVol, judge // Judge model gets updated only
return newVol // Vol gets update only
return{vol: newVol, judges: judge } // i have tried this too
},
},
};
i will like to push newCase to the Vol and also to the Judge models
Here is my best answer as I am not sure exactly what you are asking.
const Vol = require("../models/Vol");
const Case = require("../models/Cases");
const Judge = require("../models/Judge");
module.exports = {
Mutation: {
addCase: async (_, { title, volID, judgesID }) => {
const newCase = await new Case({
title: title,
vol: volID,
judges: judgesID,
}).save();
const newVol = Vol.updateOne(
{ _id: volID },
{ $push: { cases: newCase } },
{ new: true },
);
const judge = Judge.updateOne(
{ _id: judgesID },
{ $push: { cases: { $each: [newCase], $position: 0 } } },
{ new: true },
);
return newVol, judge // Judge model gets updated only
return newVol // Vol gets update only
return{vol: newVol, judges: judge } // i have tried this too
},
},
};
should look like this
const Vol = require("../models/Vol");
const Case = require("../models/Cases");
const Judge = require("../models/Judge");
module.exports = {
Mutation: {
addCase: async (_, { title, volID, judgesID }) => {
const newCase = await new Case({
title: title,
vol: volID,
judges: judgesID,
}).save();
const newVol = await Vol.updateOne(
{ _id: volID },
{ $push: { cases: newCase } },
{ new: true },
);
const judge = await Judge.updateOne(
{ _id: judgesID },
{ $push: { cases: { $each: [newCase], $position: 0 } } },
{ new: true },
);
return newCase;
},
},
};
Also because this is GraphQL it would help if I knew the return type. I would think that the return type is the Case not the newVol or judge. So in this case you would actually get weird looking results be returning something other than case. In which case the last line should read return newCase;