简体   繁体   中英

Vue JS computed property is undefined (async axios call in vuex)

So the problem is that all computed properties which have axios calls in store are running fine except a specific one. GetSong.owner is undefined but the thing is data is displayed properly on the webpage. The error is only on refresh so i guess it has something to do with the dom being loaded before the axios call in store finishes up. Any ideas? Here is the code:

<div>
   <div class="song">
       <div class="song_container">
           <div class="song_info">
               <h1 v-if="getSong" class="song_info-author">{{ getSong.owner.profile[0].name ? getSong.owner.profile[0].name : '' }}</h1>
               <h1 class="song_info-title">{{  getSong.title }} </h1>
               <h1 class="song_info-genre">{{  getSong.genre }} </h1>
           </div>
           <div class="song_playback">
               <app-player></app-player>
           </div>
        <img v-bind:class="{'liked': getLiked}" @click.prevent="likeUnlikeSong" 
       class="song_like" src="../assets/icons/favourite.svg" alt="" srcset="">
       </div>
       <app-comment></app-comment>
   </div>
</div>
</template>

<script>
import Comment from "../components/Comment.vue";
import Player from "../components/Player.vue";
export default {
    components : {
       appComment : Comment,
       appPlayer : Player
   },
   computed: {
       getSong(){
           return this.$store.getters.getSong
       },
       getLiked() {
           return this.$store.getters.getLiked
       },
       getProfile() {
           return this.$store.getters.getProfile
       },
       getImage() {
       return this.$store.getters.getImage
       }
   },
   methods : {
         likeUnlikeSong (){
           if(!this.getLiked){
               this.$store.dispatch('LIKE_SONG',this.$route.params.id)
           } else {
               this.$store.dispatch('UNLIKE_SONG',this.$route.params.id)
           }
       },
   },
   created() {
        this.$store.dispatch('GET_SONG',this.$route.params.id)
        this.$store.dispatch('CHECK_LIKE',this.$route.params.id)

   }

}
</script>

<style lang="scss" scoped>
@import '../scss/components/song';

</style> 

Store:


const song = {
    state : {
        song : {},
        songs: [],
        my_songs: [],
        liked: false
    },
    getters : {
        getSong : (state) => {
            return state.song
        },
        getAllSongs: (state) => {
            return state.songs
        },
        getLiked : (state) => {
            return state.liked
        },
        getMySongs : (state) => {
            return state.my_songs
        }
    },
    mutations : {
        setSongOne : (state,song) => {
            state.song = song
        },
        removeSongOne : (state) => {
            state.song = null
        },
        setAllSongs : (state,songsArray) => {
           state.songs = songsArray
        },
        emptyAllSongs: (state) => {
            state.songs = []
        },
        setLike: (state,boolean) => {
            state.liked = boolean
        },
        setMySongs : (state,songs) => {
            songs.forEach((song) => {
                state.my_songs.push(song)
            })
        },
        reset: (state) => {
            state.song = {},
            state.songs = [],
            state.my_songs = [],
            state.liked =  false
        }
    },
    actions : {
        UPLOAD : ({commit}, payload) => {
            return new Promise((resolve,reject) => {
                Axios.post('/song',payload)
                .then((success) => {
                   resolve()
                }).catch((error) => {
                    reject(error)
                })

            })

        },
        GET_SONG: ({commit}, payload) => {
                Axios.get(`/song/${payload}`).then(({data,status}) => {
                    if(status===200){
                        commit('setSongOne',data)
                    } 
                }).catch((error) => {
                })

        },
        GET_MY_SONGS : ({commit,getters}) => {
            if(getters.getMySongs.length == 0){
            return new Promise((resolve,reject) => {
                Axios.get('/song/me').then(({data,status}) => {
                    if(status=== 200) {
                        commit('setMySongs',data)
                        resolve()
                    }
                }).catch((error) => {
                    reject(error)
                })
            })}
        },
        GET_ALL_SONGS: ({commit}) => {
            let songs = []
            return new Promise((resolve,reject) => {
                Axios.get('/song').then(({data,status}) => {
                    if(status===200) {
                        songs.push(data)
                        commit('setAllSongs',songs)
                        resolve()
                    }
                }).catch((error) => {
                    reject(error)
                })
            })

        },
        EMPTY_ALL_SONGS: ({commit}) => {
            commit('emptyAllSongs')
        },
        LIKE_SONG : ({commit},payload) => {
             return new Promise((resolve,reject) => {
                Axios.post(`/song/${payload}/like`).then(({status}) => {
                    if(status===200) {
                        commit('setLike',true)
                        resolve()
                    } 
                }).catch((error) => {
                    reject(error)
                })
             })          
        },
        UNLIKE_SONG : ({commit},payload) => {
            return new Promise((resolve,reject) => {
               Axios.post(`/song/${payload}/unlike`).then(({status}) => {
                   if(status===200) {
                       commit('setLike',false)
                       resolve()
                   }
               }).catch((error) => {
                reject(error)
            })
            })          
       },
        CHECK_LIKE: ({commit},payload) => {
            return new Promise((resolve,reject) => {
                Axios.get(`/song/${payload}/check_like`).then(({status,data}) => {
                    if(status===200) {
                        commit('setLike',data.liked)
                        resolve()
                    } 
                }).catch((error) => {
                    reject(error)
                })
            })
        }
    }
}

export default song

As you said, it's rendering before the data has loaded.

The problem is that you're defaulting the store state to song: {} , so v-if="getSong" is always going to be true.

A quick fix would be v-if="getSong.owner" instead. Personally I wouldn't fix it that way, I'd change it to song: null and handle the missing song higher up the template. Ultimately you need to make a decision about what you want to render while the data is loading. Locally that might take milliseconds but you should consider how long it might take in production and what you want your users to see.

On an unrelated note, you shouldn't be wrapping axios calls in promises. Axios returns promises anyway so you should just return those. eg:

UPLOAD ({commit}, payload) {
  return Axios.post('/song', payload)
})

Update:

The created hook will run before rendering occurs but you're making asynchronous requests and it won't wait for those to complete before proceeding with rendering.

At the point it first renders the store state will still contain your default values. In most places the way your template is handling those defaults won't cause an error, except for the getSong.owner.profile part. getSong will be an empty object, so getSong.owner will be undefined , leading to an error trying to access its profile property.

Note that just because the other values aren't causing an error doesn't necessarily mean they are working 'correctly'. While the real values are loading the user will see a partially populated UI. This is unlikely to be desired in the finished application.

When the real values load it will render again, this time without errors. A setTimeout is unnecessary, you just need to write your template to handle the missing data.

One way to do this, as mentioned earlier, is to set song: null and move the v-if="getSong" further up the template, so that nothing tries to use getSong .

Okay so i fixed the problem. For all those who are having the same problem and work with express and mongoose. It seems mongoose was causing the problem because i had 2 populates in 1 call. I changed the order in which the populates happened and somehow the error disappeared.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM