Promise not waiting for store.dispatch - vue.js

I've just started learning Vue, and am working on porting my django project to Vue. I started out simple. My objective is to create a component, which on loading, would use Axios to fetch a list of patients from my server.
So in my component, I wrote:
export default {
data() {
return {
patients: [],
};
},
created() {
console.log(`Created Component:Patient Waiting`);
this.$store
.dispatch("getPatientList", this.today)
.then(() => {
console.log(`Finished dispatch of getPatientList from component.`);
this.patients = this.$store.getters.patientNotSeenList;
console.log(`Now, this.patients is:`);
console.log(this.patients);
})
.catch((error) => {
console.log("Got error 2");
console.log(error);
});
},
};
Template:
<p v-for="patient in patients" :key="patient.checkinno">
{{ patient.checkinno }}
</p>
In my vuex store, I have:
export default createStore({
state: {
},
getters: {
patientNotSeenList: (state) => {
console.log(`In store, in getter:patientNotSeenList:`);
return state.patientNotSeenList;
},
},
mutations: {
STORE_PATIENT_LIST(state, data) {
state.patientSeenList = data.seen
state.patientNotSeenList = data.notseen
},
},
actions: {
getPatientList({ commit }, date) {
console.log(`[In Store::getPatientList, getting patient list...]`);
axios
.get(constants.API_GETPATIENT_LIST, {
params: {
....
},
})
.then(({ data }) => {
console.log(`Got data is`);
console.log(data);
let patientSeen = data.results.filter(
(checkin) => checkin.consulted == 1
);
let patientNotSeen = data.results.filter(
(checkin) => checkin.consulted == 0
);
console.log(`patientSeen is`);
console.log(patientSeen);
console.log(`patientNotSeen is`);
console.log(patientNotSeen);
console.log(`[Finished action for Store::getPatientList]`);
commit("STORE_PATIENT_LIST", {
seen: patientSeen,
notseen: patientNotSeen,
});
})
.catch((error) => {
console.log(
"In Store::getPatientList,Could not get data from API. Maybe not logged in, or dont have token?"
console.log(error);
)})
},
}
The problem I am having is that even though I am using a promise, the data is being rendered before the action is completed and mutation commited from store.
My console log looks like this:
Created Component: Patient Waiting
index.js?4360:141 [In Store::getPatientList, getting patient list...]
PatientWaiting.vue?110a:144 Finished dispatch of getPatientList from component.
index.js?4360:33 In store, in getter:patientNotSeenList:
PatientWaiting.vue?110a:146 Now, this.patients is:
PatientWaiting.vue?110a:147 undefined
TopBar.vue?92f9:90 Route location from TopBar: PatientWaiting
index.js?4360:150 Got data is
index.js?4360:151 {count: 1, next: null, previous: null, results: Array(1)}
index.js?4360:152 Results is
index.js?4360:153 [{…}]
index.js?4360:160 patientSeen is
index.js?4360:161 []
index.js?4360:162 patientNotSeen is
index.js?4360:163 [{…}]
index.js?4360:164 [Finished action for Store::getPatientList]
So I end up with an empty list. Why is this going wrong?

You are not returning the Promise axios.get(..).then(..) creates in getPatientList({ commit }, date) and thus the then() in your Component is immediately called. Change getPatientList to:
getPatientList({ commit }, date) {
console.log(`[In Store::getPatientList, getting patient list...]`);
return axios.get(constants.API_GETPATIENT_LIST, {
params: {
....
},
}).then(({ data }) => {
console.log(`Got data is`);
console.log(data);
let patientSeen = data.results.filter(
(checkin) => checkin.consulted == 1
);
let patientNotSeen = data.results.filter(
(checkin) => checkin.consulted == 0
);
console.log(`patientSeen is`);
console.log(patientSeen);
console.log(`patientNotSeen is`);
console.log(patientNotSeen);
console.log(`[Finished action for Store::getPatientList]`);
commit("STORE_PATIENT_LIST", {
seen: patientSeen,
notseen: patientNotSeen,
});
})
.catch((error) => {
console.log(
"In Store::getPatientList,Could not get data from API. Maybe not logged in, or dont have token?"
console.log(error);
)})
},

Related

How can I update the comments without refreshing it?

First, I'm using vuex and axios.
store: commentService.js
components:
CommentBox.vue (Top components)
CommentEnter.vue (Sub components)
This is the logic of the code I wrote.
In the store called commentService.js, there are mutations called commentUpdate.
And There are actions called postComment and getComment.
At this time, In the component called CommentBox dispatches getComment with async created().
Then, in getComment, commentUpdate is commited and executed.
CommentUpdate creates an array of comments inquired by getComment and stores them in a state called commentList.
Then I'll get a commentList with "computed".
CommentEnter, a sub-component, uses the commentList registered as compounded in the CommentBox as a prop.
The code below is commentService.js.
import axios from 'axios'
export default {
namespaced: true,
state: () => ({
comment:'',
commentList: []
}),
mutations: {
commentUpdate(state, payload) {
Object.keys(payload).forEach(key => {
state[key] = payload[key]
})
}
},
actions: {
postComment(state, payload) {
const {id} = payload
axios.post(`http://??.???.???.???:????/api/books/${id}/comments`, {
comment: this.state.comment,
starRate: this.state.starRate
}, {
headers: {
Authorization: `Bearer ` + localStorage.getItem('user-token')
}
})
.then((res) => {
console.log(res)
this.state.comment = ''
this.state.starRate = ''
)
.catch((err) => {
alert('댓글은 한 책당 한 번만 작성할 수 있습니다.')
console.log(err)
this.state.comment = ''
this.state.starRate = ''
})
},
async getComment({commit}, payload) {
const {id} = payload
axios.get(`http://??.???.???.???:????/api/books/${id}/comments`)
.then((res) => {
console.log(res)
const { comment } = res.data.commentMap
commit('commentUpdate', {
commentList: comment
})
})
.catch((err) => {
console.log(err)
commit('commentUpdate', {
commentList: {}
})
})
}
}
}
The code below is CommentBox.vue
computed: {
commentList() {
return this.$store.state.commentService.commentList
}
},
methods: {
async newComment() {
if(this.$store.state.loginService.UserInfoObj.id === '') {
alert('로그인 후 이용할 수 있습니다.')
return
}
this.$store.dispatch('commentService/postComment', {
id: this.$route.params.id,
comment: this.$store.state.comment,
starRate: this.$store.state.starRate
})
}
},
async created() {
this.$store.dispatch('commentService/getComment', {
id: this.$route.params.id
})
}
The code below is CommentEnter.vue
created() {
this.userComment = this.comment
},
props: {
comment: {
type: Object,
default: () => {}
}
},
I asked for a lot of advice.
There were many comments asking for an axios get request after the axios post request was successful.
In fact, I requested an axios get within .then() of the axios post, and the network tab confirmed that the get request occurred normally after the post request.
But it's still not seen immediately when I register a new comment.
I can only see new comments when I refresh it.
How can I make a new comment appear on the screen right away when I register it?
Can't you just call getComment when postComment is finished?
methods: {
async newComment() {
if(this.$store.state.loginService.UserInfoObj.id === '') {
alert('로그인 후 이용할 수 있습니다.')
return
}
this.$store.dispatch('commentService/postComment', {
id: this.$route.params.id,
comment: this.$store.state.comment,
starRate: this.$store.state.starRate
}).then(function() {
this.$store.dispatch('commentService/getComment', {
id: this.$route.params.id
})
})
}
},
}
Or since you're using async:
methods: {
async newComment() {
if(this.$store.state.loginService.UserInfoObj.id === '') {
alert('로그인 후 이용할 수 있습니다.')
return
}
await this.$store.dispatch('commentService/postComment', {
id: this.$route.params.id,
comment: this.$store.state.comment,
starRate: this.$store.state.starRate
})
this.$store.dispatch('commentService/getComment', {
id: this.$route.params.id
})
}
},
}

Copy of store not updated when mounted Async axios

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
})
}

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)
})

pass computed property as method parameter?

So I have a store with values:
export const store = new Vuex.Store({
state: {
selectedGradeId: null,
},
getters:{
selectedGradeId: state => {
return state.selectedGradeId
},
},
mutations:{
SET_SELECTED_GRADE_ID(state, gradeid){
state.selectedGradeId = gradeid
},
CLEAR_SELECTED_GRADE_ID(state){
state.selectedGradeId = null
},
},
actions:{
loadStudentsForGrade (gradeId) {
return new Promise((resolve, reject) => {
axios.get('/students/'+gradeId)
.then((response)=>{
... do stuff
resolve(response)
}, response => {
reject(response)
})
})
},
}
})
and inside my component i basically have a select that loads the student list for the particular grade:
<select id="grades" name="grades" v-model="selectedGradeId" #change="loadStudentsForGrade(selectedGradeId)"
methods: {
loadStudentsForGrade(gradeId) {
this.$store.dispatch('loadStudentsForGrade', {gradeId})
.then(response => {
}, error => {
})
},
},
computed: {
selectedGradeId: {
get: function () {
return this.$store.getters.selectedGradeId;
},
set: function (gradeId) {
this.$store.commit('SET_SELECTED_GRADE_ID', gradeId);
}
},
}
when the 'loadStudentsForGrade' method is called in my component, it takes 'selectedGradeId' as a parameter, which is a computed property.
Now the problem I have is that inside my store, the action 'loadStudentsForGrade' gets an object( i guess computed?) instead of just the gradeid
object i get is printed to console:
{dispatch: ƒ, commit: ƒ, getters: {…}, state: {…}, rootGetters: {…}, …}
The first parameter of your action is the store, and the second the payload.
so you should do :
actions:{
// here: loadStudentsForGrade (store, payload) {
loadStudentsForGrade ({ commit }, { gradeId }) {
return new Promise((resolve, reject) => {
axios.get('/students/'+gradeId)
.then((response)=>{
//... do stuff
//... commit('', response);
resolve(response)
}, response => {
//... commit('', response);
reject(response)
})
})
},
}
Related page in the docs :
https://vuex.vuejs.org/en/actions.html#dispatching-actions