Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Vue.js: Data is not updating with state change so the re-render does not happen

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:

vuex Vuex ..but, videos[] is never updated thus the video component never gets the props new props etc..

A couple of notes:

  • already tried, hiding the child component with v-if (and showing after state change)
  • tried setTimeout to test things, but the data will come through and then the videoJS player never instantiates correctly (must have initial data)
  • tried doing this with a local method / not using Vuex state
  • console is showing error 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 } 
like image 381
Jordan Avatar asked Mar 19 '18 01:03

Jordan


People also ask

How do I force re render in Vue?

The best way to force Vue to re-render a component is to set a :key on the component. When you need the component to be re-rendered, you just change the value of the key and Vue will re-render the component.

Does changing props cause re render Vue?

The changed prop is not used by the render function so rendering should not be triggered. There are several use cases for props that don't require rendering updates. e.g.: The prop could be an id used to load data behind the scenes.

How do I force a Vue refresh page?

You can force-reload components by adding :key="$route. fullPath".

Does VUEX keep state on refresh?

To persist Vuex state on page refresh, we can use the vuex-persistedstate package. import { Store } from "vuex"; import createPersistedState from "vuex-persistedstate"; import * as Cookies from "js-cookie"; const store = new Store({ // ...


2 Answers

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"> 

Update:

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"> 



Vue's reactivity

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 using Vue.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!

like image 131
acdcjunior Avatar answered Sep 30 '22 01:09

acdcjunior


So turns out the whole issue was with video.js. I'm half tempted to delete this question, but I would not want anyone who helped to lose any points.

The solution and input here did help to rethink my use of watchers or how I was attempting this. I ended up just using the central store for now since it works fine, but this will need to be refactored later.

I had to just forego using video.js as a player for now, and a regular html5 video player works without issues.

like image 24
Jordan Avatar answered Sep 30 '22 00:09

Jordan