Copy of store not updated when mounted Async axios - vue.js

I have been struggling with this issue for a day now. I want to make a copy of the store for user into userCopy so that it can be edited by the user without causing a mutation. My problem is that even though I am using the mounted hook, userCopy only returns an empty store state.
pages/settings/_id.vue
<template>
<div>
{{ user }} // will display the whole object
{{ userCopy }} // will only display empty store object
</div>
</template>
<script>
import { mapState } from 'vuex'
import _ from 'lodash'
data() {
return {
userCopy: {}
}
},
computed: {
...mapState({ user: (state) => state.staff.user })
},
created() {
this.$store.dispatch('staff/fetchUser', this.$route.params.id)
},
mounted() {
this.$data.userCopy = _.cloneDeep(this.$store.state.staff.user)
},
</script>
store/staff.js
import StaffService from '~/services/StaffService.js'
export const state = () => ({
user: {
offers: '',
legal: ''
}
})
export const mutations = {
SET_USER(state, user) {
state.user = user
},
}
export const actions = {
fetchUser({ commit, getters }, id) {
const user = getters.getUserById(id)
if (user) {
commit('SET_USER', user)
} else {
StaffService.getUser(id) // StaffService users axios get call
.then((response) => {
commit('SET_USER', response.data)
})
.catch((error) => {
console.log('There was an error:', error.response)
})
}
},
}
export const getters = {
getUserById: (state) => (id) => {
return state.staff.find((user) => user.id === id)
}
}
Even using this mounted method did not solve the issue. The userCopy object still returns empty.
mounted() {
this.$store
.dispatch('staff/fetchUser', this.$route.params.id)
.then((response) => {
this.userCopy = this.$store.state.staff.user
})
},

It seems that the mounted() is called before your network request get solved.
To fix this, I suggest to do like this.
First:
if (user) {
console.log('user found',user)
commit('SET_USER', user)
return user
} else {
console.log('user not found')
//RETURN the Axios Call here
return StaffService.getUser(id) // StaffService users axios get call
.then((response) => {
commit('SET_USER', response.data)
//return the response here, after committing
return response.data
})
then in your component
mounted() {
this.$store
.dispatch('staff/fetchUser', this.$route.params.id)
.then((response) => {
console.log(response)
this.userCopy = response
})
}

Related

Problems with vuex: Uncaught (in promise) TypeError: _ctx.getProduct.image_url is undefined

I'm trying to load a product's data, passing props through the Vue Router, this is my code:
...store/modules/products_manager.js
import axios from 'axios';
const BASE_URL = "http://127.0.0.1:3000/"
const state = {
product: [],
products: [],
category: "",
}
const getters = {
getProducts(state) {
return state.products;
},
getProduct(state) {
console.log("GET")
console.log(state.product)
return state.product;
},
getCategory(state) {
return state.category;
},
}
const actions = {
getAllProducts({ commit }, payload) {
const config = {
params: {
title: payload['name']
}
}
new Promise((resolve, reject) => {
axios
.get(`${BASE_URL}products/`, config)
.then((response) => {
commit("setProducts", response);
resolve(response.data);
})
.catch((error) => {
reject(error);
});
});
},
getProductById({ commit }, payload) {
new Promise((resolve, reject) => {
axios
.get(`${BASE_URL}products/${payload}`)
.then((response) => {
commit("setProduct", response);
console.log("PRODUCT")
console.log(response.data)
resolve(response.data);
})
.catch((error) => {
reject(error);
});
});
},
getCategoryById({ commit }, payload) {
new Promise((resolve, reject) => {
axios
.get(`${BASE_URL}categories/${payload}`)
.then((response) => {
commit("setCategory", response);
resolve(response.data);
})
.catch((error) => {
reject(error);
});
});
},
}
const mutations = {
setProducts(state, data) {
state.products = data.data;
},
setProduct(state, data) {
console.log("SET")
console.log(data.data)
state.product = data.data;
},
setCategory(state, data) {
state.category = data.data;
},
}
export default {
state,
getters,
actions,
mutations,
}
../components/ProductPage.vue
<template lang="">
<div>
<p v-if="(getProduct != null || getProduct != undefined)" >{{ getProduct['image_url'][0] }}</p>
</div>
</template>
<script>
import { mapActions, mapGetters } from 'vuex';
export default {
name: 'Product',
props: ["id", "category_id"],
computed: {
...mapGetters(["getProduct", "getCategory"]),
},
mounted() {
console.log(this.id)
console.log(this.category_id)
this.$store.dispatch("getProductById", this.id)
this.$store.dispatch("getCategoryById", this.category_id)
}
}
</script>
But I'm having some problems, I'm starting with vuex and I'm not understanding many things yet.
Error Printscreen
I did some tests, I used functions like created, updated, etc. With some of them, the information was even displayed on the screen, but it still generated the same errors. I believe it must be some error in the vue data flow, but I still don't understand how to solve it.
Sorry my bad english ;)
Solution:
<div>
<p v-if="(getProduct['image_url'] != null || getProduct['image_url'] != undefined)" >{{ getProduct['image_url'][0] }}</p>
</div>
Thank's #yoduh

Vue.js with Axios use data from other method

I have a external api which returns a json of a user with some attributes like username. I want to use this username in my vue methods as a url parameter and defined the function getUser(). My problem is that the parameter keeps undefined
Here is my code
<script>
import Axios from 'axios-observable'
export default {
data () {
return {
appointments: {},
event_counter: 0,
user: ''
},
methods: {
getUser: function () {
Axios
.get('http://127.0.0.1:5000/users/get_user')
.subscribe(response => { this.user = response.data.username })
},
getAppointments: function () {
Axios
.get('http://127.0.0.1:5000/appointments/get_appointments?user=' + this.user)
.subscribe(response => { this.appointments = response.data })
},
fetchData: function () {
setInterval(() => {
this.getAppointments()
}, 150000)
}
},
mounted () {
//this.user = this.getUser()
this.getUser()
this.fetchData()
},
created () {
//this.user = this.getUser()
this.getUser()
this.getAppointments()
}
}
</script>
I tried some variants with return response.data or data: this.getUser() etc. Obtaining the user in template with {{ user }} works fine but isn't helpful. I don't have any syntax or runtime error from vue/electron-vue
Any idea?
Finally got a solution!
<script>
import Axios from 'axios'
export default {
data () {
return {
appointments: {},
event_counter: 0,
user: 'test'
}
},
methods: {
getUser: function () {
return Axios
.get('http://127.0.0.1:5000/users/get_user')
.then(response => {
this.user = response.data.username
return this.user
})
},
getAppointments: function () {
this.getUser()
.then(data => {
let url = 'http://127.0.0.1:5000/appointments/get_appointments?user=' + data
Axios
.get(url)
.then(response => { this.appointments = response.data })
})
},
fetchData: function () {
setInterval(() => {
this.getAppointments()
}, 150000)
}
},
mounted () {
this.fetchData()
},
created () {
this.getAppointments()
}
}
</script>
The solution was to change the call of the getUser() and retrieve the date in the arrow function block .then(data =>).
The answer of #loan in this Issue give me the hint: How to set variable outside axios get.
Thanks a lot to all.
<script>
import Axios from 'axios-observable'
export default {
data () {
return {
appointments: {},
event_counter: 0,
user: ''
},
computed: {
updatedUrl: {
return `http://127.0.0.1:5000/appointments/get_appointments?user=${this.user}`
}
},
methods: {
forceGetUsername() {
return this.user
},
getUser: function () {
Axios
.get('http://127.0.0.1:5000/users/get_user')
.subscribe(response => { this.user = response.data.username })
},
getAppointments: function () {
console.log(updatedUrl)
Axios
.get(updatedUrl)
.subscribe(response => { this.appointments = response.data })
},
// Below can remain the same
}
</script>
So it seems the url is being cached and not updated once created. So I added new function to ensure the latest value is being returned. Not very ideal.
Added the URL to computed property. If this doesn't work then I am lost as well :(

Vuex update state by using store actions

I have two functions in my store, one that gets data by calling API and one that toggles change on cell "approved". Everything working fine, except that when I toggle this change it happens in database and I get the response that it is done but It doesn't update on UI.
I am confused, what should I do after toggling change to reflect change on UI, should I call my API from .then or should I call action method responsible for getting data from server.
export default {
state: {
drivers: {
allDrivers:[],
driversError:null
},
isLoading: false,
token: localStorage.getItem('token'),
driverApproved: null,
driverNotApproved: null
},
getters: {
driversAreLoading (state) {
return state.isLoading;
},
driverError (state) {
return state.drivers.driversError;
},
getAllDrivers(state){
return state.drivers.allDrivers
}
},
mutations: {
getAllDrivers (state) {
state.isLoading=true;
state.drivers.driversError=null;
},
allDriversAvailable(state,payload){
state.isLoading=false;
state.drivers.allDrivers=payload;
},
allDriversNotAvailable(state,payload){
state.isLoading=false;
state.drivers.driversError=payload;
},
toggleDriverApproval(state){
state.isLoading = true;
},
driverApprovalCompleted(state){
state.isLoading = false;
state.driverApproved = true;
},
driverApprovalError(state){
state.isLoading = false;
state.driverError = true;
}
},
actions: {
allDrivers (context) {
context.commit("getAllDrivers")
return new Promise((res,rej)=>{
http.get('/api/admin/getAllDrivers').then(
response=>{
if (response.data.success){
let data=response.data.data;
data=data.map(function (driver) {
return {
/* response */
};
});
context.commit("allDriversAvailable",data);
res();
}else {
context.commit("allDriversNotAvailable",response.data)
rej()
}
})
.catch(error=>{
context.commit("allDriversNotAvailable",error.data)
rej()
});
});
},
toggleDriverApproval (context, payload){
return new Promise((res, rej)=>{
http.post("/api/admin/toggleDriverApproval",{
driver_id: payload
})
.then( response => {
context.commit('driverApprovalCompleted');
res();
}).catch( error =>{
context.commit('driverApprovalError');
rej()
})
})
}
}
}
and here is the code on the view, I wrote the necessary code for better clarification of the problem
export default {
name: 'Drivers',
data: () => ({
data: [],
allDrivers: [],
driversErrors: []
}),
created() {
this.$store
.dispatch('allDrivers')
.then(() => {
this.data = this.$store.getters.getAllDrivers
})
.catch(() => {
this.errors = this.$store.getters.driverError
})
},
computed: {
isLoading() {
return this.$store.getters.driversAreLoading
}
},
methods: {
verify: function(row) {
console.log(row)
this.$store.dispatch('toggleDriverApproval', row.id).then(() => {
this.data = this.$store.getters.getAllDrivers
console.log('done dis')
})
},
},
}
if I understand your issue, you want the UI displaying your data to change to the updated data after making a post request.
If you are using Vuex you will want to commit a mutation, and use a getter display the data.
I am not sure how your post request is being handled on the server but if successful typically you would send a response back to your front end with the updated data, and commit a mutation with the updated data.
Example:
Make a Post request
toggleDriverApproval (context, payload){
return new Promise((res, rej)=>{
http.post("/api/admin/toggleDriverApproval",{
driver_id: payload
})
.then( response => {
context.commit('driverApprovalCompleted', response.data);
res();
}).catch( error =>{
context.commit('driverApprovalError', error.response.data);
rej()
})
})
}
If succesful commit the mutation
.then( response => {
context.commit('driverApprovalCompleted', response.data);
res();
})
response.data being your data you want to mutate the state with.
Mutation Example:
customMutation(state, data) {
state.driverApproval = data
}
Getter Example:
driver(state) {
return state.driverApproval
}
displaying the getter in a template
<template>
<div v-if="driver">{{driver}}</div>
</template>
<script>
import {mapGetters} from 'vuex'
export default {
name: Example,
computed: {
driver() {
return this.$store.getters.driver
},
// or use mapGetters
...mapGetters(['driver'])
}
}
</script>
more examples can be found at Vuex Docs

Correct way to do a redirect after posting through axios in a vuex store

I am using nuxtjs, axios and vuex to post from a form component to post my data to my backend.
When posted I'd like to redirect to the view record screen and populate it with the returned information using the ID to navigate there
so my path might be /cases/14325 (if 14325 is the id returned once created)
What is the correct way to do this please
I have the following code in my vuex store
export const state = () => ({
cases: []
})
// *** MUTATIONS ***
export const mutations = {
add(state, newCase ) {
state.cases.push(newCase)
},
}
// *** ACTIONS ***
export const actions = {
addCase(context, newCase) {
const createdCase = {
...newCase
}
axios.post("http", createdCase)
.then(result => {
context.commit('add', {...createdCase, id: result.data.name})
})
.catch(e => console.log(e));
},
}
In my component I have the following
import { mapMutations, mapGetters, mapActions } from 'vuex'
export default {
data () {
return {
newCase: {
caseName: '',
summary: '',
status: 'live',
},
}
},
methods: {
...mapActions([
'addCase'
]),
onSubmit() {
// Save the post
this.$store.dispatch('addCase').then(path => {
this.$router.redirect(path)
}).catch((err) => {
console.log(err)
})
},
}
}
</script>
How do i return the new id from my store please and replace cases/1 with '/cases/' + new id?
Thanks for the help as always
Maybe is will be enough when you improve your action this way:
addCase(context, newCase) {
return new Promise ((resolve, reject) => {
const createdCase = {...newCase}
axios.post('http', createdCase).then(result => {
context.commit('add', {...createdCase, id: result.data.name})
resolve(/*path*/)
}).catch(e => {
console.log(e)
reject(/*reason*/)
})
})
}
And then you use it this way:
this.$store.dispatch('addCase', context).then(path => {
this.$router.redirect(path)
})

VueJs. How to close pre-loader after data from server have been loaded

I use VueX in my VueJs app and I need to close pre-loader after I got an answer from server for 4 my get requests. I try to use callback function to change pre-loader state but it changes after requests STARTs, but I need to change pre-loader state after all requests SUCCESS. Below is my code:
Index.vue
<template>
<div class="index">
<div class="content-is-loading"
v-if="appIsLoading"></div>
<div v-else class="index__wrapper">
<navbarInner></navbarInner>
<div class="index__content">
<sidebar></sidebar>
<router-view></router-view>
</div>
<foo></foo>
</div>
</div>
</template>
<script>
import NavbarInner from './NavbarInner'
import Sidebar from './Sidebar'
import Foo from './../Foo'
import Shows from './Shows/Shows'
import Dashboard from './Dashboard'
import { API_URL } from '../../../config/constants'
import { mapState } from 'vuex'
export default {
name: 'index',
data () {
return {
appIsLoading: true,
bandName: ''
}
},
components: {
NavbarInner,
Sidebar,
Foo,
Shows,
Dashboard
},
created () {
function loadData (context, callback) {
// Loading bands for the user
context.$store.dispatch('getBands')
// Loading contacts for the user
context.$store.dispatch('getContacts')
// Loading merch for the user
context.$store.dispatch('getInventory')
// Loading tours for the active band
context.$store.dispatch('getToursList')
callback(context)
}
loadData(this, function (context) {
context.appIsLoading = false
})
}
}
Below I add code of one of the request:
api/tour.js
import axios from 'axios'
import { API_URL } from '../../config/constants'
export default {
getToursList () {
return new Promise((resolve, reject) => {
let bandId = window.localStorage.getItem('active_band_id')
let token = window.localStorage.getItem('token')
axios.get(API_URL + '/api/bands/' + bandId + '/tours/', {
headers: {'x-access-token': token}
})
.then((result) => {
return resolve(result.data)
})
.catch(err => reject(err))
})
},
getInventory () {
return new Promise((resolve, reject) => {
let token = window.localStorage.getItem('token')
axios.get(API_URL + '/api/merch/listProductForUser/1000/0', {
headers: {'x-access-token': token}
})
.then((response) => {
let items = response.data
return resolve(items)
})
.catch((err) => {
return reject(err)
})
})
},
getContacts () {
return new Promise((resolve, reject) => {
let token = window.localStorage.getItem('token')
axios.get(API_URL + '/api/contact/get_contacts_for_user/1000/0', {
headers: {'x-access-token': token}
})
.then((response) => {
console.log(response.data)
let contacts = response.data
return resolve(contacts)
})
.catch((err) => {
return reject(err)
})
})
},
getBands () {
return new Promise((resolve, reject) => {
let token = window.localStorage.getItem('token')
axios.get(API_URL + '/api/band/getBandsForUser/1000/0', {
headers: {'x-access-token': token}
})
.then((response) => {
console.log(response.data)
let bands = response.data
return resolve(bands)
})
.catch((err) => {
return reject(err)
})
})
}
}
Vuex/tour.js
import api from '../../api/onload'
import * as types from '../mutation-types'
const state = {
tours: [],
contacts: [],
bands: [],
merch: [],
success: false,
loading: false
}
const actions = {
getToursList ({commit}) {
api.getToursList()
.then((tours) => {
commit(types.RECEIVE_TOURS, tours)
}).catch((err) => {
console.error('Error receiving tours: ', err)
commit(types.RECEIVE_TOURS_ERROR)
})
},
getInventory ({commit}) {
api.getInventory()
.then((items) => {
commit(types.RECEIVE_INVENTORY, items)
})
.catch((err) => {
console.error('Error receiving inventory: ', err)
commit(types.RECEIVE_INVENTORY_ERROR)
})
},
getBands ({commit}) {
api.getBands()
.then((bands) => {
commit(types.RECEIVE_BANDS, bands)
})
.catch((err) => {
console.error('Error receiving bands: ', err)
commit(types.RECEIVE_BANDS_ERROR)
})
},
getContacts ({commit}) {
api.getContacts()
.then((contacts) => {
commit(types.RECEIVE_CONTACTS, contacts)
})
.catch((err) => {
console.error('Error receiving bands: ', err)
commit(types.RECEIVE_CONTACTS_ERROR)
})
}
}
const mutations = {
[types.RECEIVE_TOURS] (state, tours) {
state.tours = tours
},
[types.RECEIVE_INVENTORY] (state, items) {
state.items = items
},
[types.RECEIVE_BANDS] (state, bands) {
state.bands = bands
},
[types.RECEIVE_CONTACTS] (state, contacts) {
state.contacts = contacts
console.log(state.contacts)
}
}
export default {
state, mutations, actions
}
How should I change the code?
The code you posted doesn't actually wait on the response from any of the actions you are calling.
You could also move everything to a method and refactor.
Finally I've assumed your actions return a Promise i.e.
created () {
this.getAll()
},
methods: {
getAll () {
Promise.all([
this.$store.dispatch('getBands'),
this.$store.dispatch('getContacts'),
this.$store.dispatch('getInventory'),
this.$store.dispatch('getToursList'),
])
.then(responseArray => {
this.appIsLoading = false
})
.catch(error => { console.error(error) })
EDIT
To get your actions to resolve as you need them (when the mutations have fired and your store is updated) you need to wrap them in a Promise:
Vuex/tour.js (actions object)
getToursList: ({ commit }) =>
new Promise((resolve, reject) => {
api.getToursList()
.then((tours) => {
commit(types.RECEIVE_TOURS, tours)
resolve()
}).catch((err) => {
console.error('Error receiving tours: ', err)
commit(types.RECEIVE_TOURS_ERROR)
reject()
})
})