48

I am working on a Vue component that will be placed on multiple websites via a CMS system. The issue I encounter is that even if my js scripts loading order is correct, sometime I get this error:

Uncaught ReferenceError: Vue is not defined
    at HTMLDocument.<anonymous>

Vue is loaded via cdn and it's above the component's code.

All the Vue code is run like this:

document.addEventListener("DOMContentLoaded", () => {
  // here is the Vue code
});

I even added a setTimeout() inside the DOMContentLoaded event and still did not do the trick. window.onload = function() did not work either in all cases. I still got that error from time to time. The scripts are loaded in the body.

Do you have any idea what it can be another approach? I want to be sure that in the moment the Vue code is fired, Vue is loaded and ready to be initialized on the page. Thank you!

Bogdan Lungu
  • 786
  • 1
  • 9
  • 16

3 Answers3

89

use vue mounted() to run any code on page load, and updated() to run after any component operations, so a perfect solution would be combining both Roy j and vue lifecycle hooks

mounted() {
    window.addEventListener('load', () => {
         // run after everything is in-place
    })
},

// 99% using "mounted()" with the code above is enough, 
// but incase its not, you can also hook into "updated()"
//
// keep in mind that any code in here will re-run on each time the DOM change
updated() {
    // run something after dom has changed by vue
}

note that you can omit the window.addEventListener() and it still going to work but it might miss + if you are using something like jquery outerHeight(true) its better to use it inside the window event to make sure you are getting correct values.

Update 1 :

instead of window.addEventListener('load') there is also another way by using document.readyState so the above can be

mounted() {
  document.onreadystatechange = () => { 
    if (document.readyState == "complete") { 
        // run code here
    } 
  }
},

Update 2 :

the most reliable way i've found so far would be using debounce on $nextTick, so usage becomes

import debounce from 'lodash/debounce'

// bad
updated() {
    this.$nextTick(debounce(() => {
        console.log('test') // runs multiple times
    }, 250)) // increase to ur needs
}

// good
updated: debounce(function () {
    this.$nextTick(() => {
        console.log('test') // runs only once
    })
}, 250) // increase to ur needs

when using debounce with updated it gets tricky, so make sure to test it b4 moving on.

Update 3 :

you may also try MutationObserver

tony19
  • 125,647
  • 18
  • 229
  • 307
ctf0
  • 6,991
  • 5
  • 37
  • 46
  • 4
    Given that the question was asked within the context of Vue, this should be the accepted answer. – Tiago Nov 05 '17 at 18:15
  • Vue.nextick instead of setTimeout in this case. – Stark Dec 18 '17 at 04:56
  • 1
    @Johnson nexttick doesnt always work as expected & neither will timeout because the dom may take longer to finish updating, sadly vue doesnt have an event when dom has finished updating, so its kinda hit & miss – ctf0 Dec 18 '17 at 07:55
  • @ctf0 I didn't think about that. Your answer wound up not helping me at the end because I needed to make an axios call on `planner.vue` to update the vuex state, and with the code above it worked but only once and if the dom was loaded on the specific route that is using planner.vue, if I loaded the homepage and then went to /app/planner the template wouldn't load because the dom was already loaded once in the homepage. I had to end up moving the whole axios request into an Vuex action, then dispatch it right after creating the Vuex instance. This should be the accepted answer btw. Lol – Stark Dec 18 '17 at 09:00
  • @Johnson am not very familiar with vuex, but i would recommend extracting the code into its own function, and simply reference it once inside the `window.load` and another inside the axios success call, or add it inside an event & just fire that event each time you want the function to exec. – ctf0 Dec 18 '17 at 10:35
  • More simple: `document.onreadystatechange = () => { if (document.readyState == "complete") { //run code here } }` – Adriano Resende Jun 25 '18 at 16:21
  • 1
    @AdrianoResende added – ctf0 Jun 26 '18 at 09:33
  • This is exactly what i was looking for. Worked like a charm. Simple and elegant. Thanks a lot. SHould be the accepted answer since the question is related to Vue – Raj Jul 01 '20 at 09:06
  • I used this method to load Stripe elements. Loading Stripe Elements input is a bit tricky with Vue. You should be very careful if you want to use v-if to load different parts of a Checkout page. let's say if there is no product and you don't want to load your checkout form, then the approach in this post is very helpful! – Mycodingproject Jul 26 '20 at 12:57
  • 1
    If the Vue script hasn't fully loaded yet (for whatever reason), then how would any Vue stuff work? – Kalnode Sep 25 '20 at 14:36
  • @MarsAndBack nothing will work if vue wasnt correctly/fully initialized – ctf0 Oct 04 '20 at 19:00
  • Thanks for the debounce idea, perfect for me – migli Sep 10 '21 at 00:40
  • Saying "it should be the accepted answer because it was asked in the context of Vue" ignores the OP's question, which says he's getting "Vue is not defined". – Roy J Oct 18 '22 at 18:58
39

Use the load event to wait until all resources have finished loading:

<script>
  window.addEventListener("load", function(event) {
    // here is the Vue code
  });
</script>

Further explanation

DOMContentLoaded is an event fired when the HTML is parsed and rendered and DOM is constructed. It is usually fired pretty fast in the lifetime of the app. On the other hand, load is only fired when all the resources (images, stylesheets etc.) are retrieved from the network and available to the browser.

You can also use the load event for a specific script.

Community
  • 1
  • 1
Roy J
  • 42,522
  • 10
  • 78
  • 102
  • 1
    i just put console.log inside those function, nothing happen, how do i tested it work or not – Freddy Sidauruk Dec 05 '18 at 15:05
  • console.log should work for seeing if the code has run. You could also go into the browser debugger and set a break point. Maybe you should post a question. – Roy J Dec 05 '18 at 18:30
  • This does not do anything on vue3 – daveyvdweide Aug 01 '23 at 10:25
  • @daveyvdweide It is not specific to a version of Vue. The `load` event is being detected by the browser and the Vue code is not executed until it receives that event. You can read more about the load event at the link in the first sentence of the post. – Roy J Aug 02 '23 at 14:13
  • @RoyJ I found out that the load event is a bit broke in vue3, it'll fire it before the DOM is fully loaded https://github.com/vuejs/core/issues/5844 – daveyvdweide Aug 03 '23 at 07:26
2

https://v2.vuejs.org/v2/api/#mounted

this helped me

mounted: function () {
  this.$nextTick(function () {
    // Code that will run only after the
    // entire view has been rendered
  })
}
tony19
  • 125,647
  • 18
  • 229
  • 307