I have a reusable component that is a video.js video player. This component works fine when the data is passed in on the initial DOM load.
I need to figure out why my component is not re-rendering after the state is updated in Vuex.
The parent component passes down the data for the video via props. I also have this set to be used with multiple videos and it works fine with a single one or many.
<div v-for="video in videos" :key="video.id">
<video-player :videoName="video.videoName" :videoURL="video.videoURL" :thumbnail="video.thumbnail"></video-player>
</div>
I'm setting the initial state to a generic video for all users in my Vuex store.
getFreeVideo: [
{
videoName: "the_video_name",
videoURL: "https://demo-video-url.mp4",
thumbnail: "https://s3.amazonaws.com/../demo-video-poster.jpg"
}
]
This is set in data in videos
(and later set to getFreeVideo)
data () {
return {
videos: []
}
}
I'm setting videos
in data() to getFreeVideo in the store within the created() lifecycle:
this.videos = this.getFreeVideo
..and checking if a user has a personal video and updating the state in the created() lifecycle.
this.$store.dispatch('getFreeVideo', 'the_video_name')
This makes a request with axios and returns our video data successfully.
I'm using mapState import { mapState } from 'vuex
to watch for a state change.
computed: {
...mapState(['getFreeVideo'])
}
I am not seeing why this.videos
is not being updated.
Here, my assumption regarding the expected behaviour would be videos[]
being updated from the state change and a re-rendering of the component.
As you can see below, the state has been updated, and the videoUpdate()
within the computed properties has the new data as well:
..but, videos[]
is never updated thus the video component never gets the props new props etc..
A couple of notes:
v-if
(and showing after state change)setTimeout
to test things, but the data will come through and then the videoJS player never instantiates correctly (must have initial data)TypeError: Cannot read property '_withTask' of undefined
but this happens even when the demo video loads correctly, so this seem unrelated, and I can't find anything anywhere in here that presents itself as undefined.TL;DR
I basically can't get child component to re-render after the state change.
And although I can get the data into videos[]
with a different structure, it still never re-renders.
Why is the data not making it through, and the re-render never happening?
Please don't post answers that only contain links to 'understanding reactivity' or something without any explanation. 😁
appended for @acdcjunior
//action
getFreeVideo: (context, videoName) => {
axios({
method: 'post',
url: 'https://hidden-for-posting',
data: {
action: 'getVideo',
userId: '1777', // (hardcoded to test)
videoName: videoName
},
headers: {
'x-api-key': apiKey,
'Content-Type': 'application/json'
}
})
.then(response => {
let video = [
{
videoName: response.data.videoName,
videoURL: response.data.videoURLs.mp4,
thumbnail: response.data.thumbnails['1280']
}
]
return context.commit('updateGetFreeVideo', video)
})
.catch(error => {
if (error.response) {
console.log(error.response)
} else if (error.request) {
console.log(error.request)
} else {
console.log('Error', error.message)
}
console.log(error.config)
})
}
// mutation:
updateGetFreeVideo: (state, payload) => {
return state.getFreeVideo = payload
}
// getter:
getFreeVideo: state => {
return state.getFreeVideo
}
NOTE: at the bottom of this answer, see the general point I make about update/reactivity issues with Vue.
Now, about the question, based on the code you posted, considering the template:
<div v-for="video in videos" :key="video.id">
It picks the videos
from:
data () {
return {
videos: freeVideo
}
}
Although it initializes from freeVideo
, in nowhere in your code you show an update of videos
.
Solution:
You already have the state mapped in the getFreeVideo
computed:
computed: {
...mapState(['getFreeVideo'])
}
Use it:
<div v-for="video in getFreeVideo" :key="video.id">
I'm setting
videos
in data() to getFreeVideo in the store within the created() lifecycle:this.videos = this.getFreeVideo
This is not enough to keep this.videos
updated with whatever this.getFreeVideo
is. Whenever something is set to this.getFreeVideo
it will only change this.getFreeVideo
, not this.videos
.
If you want to automatically update this.videos
whenever this.getFreeVideo
changes, create a watcher:
watch: {
getFreeVideo() {
this.videos = this.getFreeVideo
}
}
And then keep using videos
in the v-for
:
<div v-for="video in videos" :key="video.id">
If your state is not getting updated in the view, perhaps you are not exploring Vue at its best:
To have Vue automatically react to value changes, the objects must be initially declared in
data
. Or, if not, they must be added usingVue.set()
.
See the comments in the demo below. Or open the same demo in a JSFiddle here.
new Vue({
el: '#app',
data: {
person: {
name: 'Edson'
}
},
methods: {
changeName() {
// because name is declared in data, whenever it
// changes, Vue automatically updates
this.person.name = 'Arantes';
},
changeNickname() {
// because nickname is NOT declared in data, when it
// changes, Vue will NOT automatically update
this.person.nickname = 'Pele';
// although if anything else updates, this change will be seen
},
changeNicknameProperly() {
// when some property is NOT INITIALLY declared in data, the correct way
// to add it is using Vue.set or this.$set
Vue.set(this.person, 'address', '123th avenue.');
// subsequent changes can be done directly now and it will auto update
this.person.address = '345th avenue.';
}
}
})
/* CSS just for the demo, it is not necessary at all! */
span:nth-of-type(1),button:nth-of-type(1) { color: blue; }
span:nth-of-type(2),button:nth-of-type(2) { color: red; }
span:nth-of-type(3),button:nth-of-type(3) { color: green; }
span { font-family: monospace }
<script src="https://unpkg.com/vue"></script>
<div id="app">
<span>person.name: {{ person.name }}</span><br>
<span>person.nickname: {{ person.nickname }}</span><br>
<span>person.address: {{ person.address }}</span><br>
<br>
<button @click="changeName">this.person.name = 'Arantes'; (will auto update because `name` was in `data`)</button><br>
<button @click="changeNickname">this.person.nickname = 'Pele'; (will NOT auto update because `nickname` was not in `data`)</button><br>
<button @click="changeNicknameProperly">Vue.set(this.person, 'address', '99th st.'); (WILL auto update even though `address` was not in `data`)</button>
<br>
<br>
For more info, read the comments in the code. Or check the docs on <b>Reactivity</b> (link below).
</div>
To master this part of Vue, check the Official Docs on Reactivity - Change Detection Caveats. It is a must read!