machine yearning
machine yearning

Reputation: 10119

Clean-up mounted()-attached script in destroyed() callback in Vue component

I am programmatically adding necessary scripts into some of my Vue components, like so:

mounted() {
    const
        googleRecaptchaScript = document.createElement('script'),
        recaptchaURL = 'https://www.google.com/recaptcha/api.js';
    googleRecaptchaScript.setAttribute('src', recaptchaURL);
    googleRecaptchaScript.setAttribute('id', 'grecaptchascript');
    document.head.appendChild(googleRecaptchaScript);
},

Now, I noticed that if I navigate away from the component and back into it, without refreshing the page wholesale, that Vue will start loading in duplicate copies of the things I attach in that mounted() call. So I wanted to clean them up as follows:

beforeDestroy() {
    const gsc = document.getElementById('grecaptchascript');
    gsc.parentElement.removeChild(gsc);
},

However it seems like the id of that script is never even set, so the cleanup step fails silently.

Am I doing it totally wrong? Is a better pattern for doing this? If not, why isn't my <script> getting the id I'm setting?

Footnote: I am aware that using an id is problematic here since we're talking about duplicated elements. I'll change this later. Please consider the general case of selecting by any arbitrary attribute, if that helps. I've tried other attributes but with no success.

Upvotes: 0

Views: 2117

Answers (2)

stdob--
stdob--

Reputation: 29172

I think that such cleaning is not necessary: after loading the script all definitions of functions and variables fall into the global scope, and after unloading the script they are not deleted.

What you need is to check that the scripts are already loaded, and do not load more than once:

mounted() {
  const gsc = document.getElementById('grecaptchascript');
  if (!gsc) {
    const
      googleRecaptchaScript = document.createElement('script'),
      recaptchaURL = 'https://www.google.com/recaptcha/api.js';
    googleRecaptchaScript.setAttribute('src', recaptchaURL);
    googleRecaptchaScript.setAttribute('id', 'grecaptchascript');
    document.head.appendChild(googleRecaptchaScript);
  }
}

Upvotes: 1

machine yearning
machine yearning

Reputation: 10119

I'm not sure what the problem was exactly, but for future reference this solution worked and seemed fairly good for my case because it selects on the prefix of the script's src attribute:

mounted() {
    const
        googleRecaptchaScript = document.createElement('script'),
        recaptchaURL = `https://www.google.com/recaptcha/api.js?hl=${this.$i18n.locale}`;
    googleRecaptchaScript.setAttribute('src', recaptchaURL);
    document.head.appendChild(googleRecaptchaScript);
},
beforeDestroy() {
    const recaptchaScripts = document.querySelectorAll('script[src^="https://www.google.com/recaptcha/api.js"]');
    for (let i = 0; i < recaptchaScripts.length; i += 1) {
        recaptchaScripts[i].parentElement.removeChild(recaptchaScripts[i]);
    }
},

I'm pretty sure id would have worked fine too, I was just being dumb. And I'd still be interested if anyone has a better solution, because "manually" (programmatically) selecting and removing elements from the page seems kinda dirty.

Upvotes: 0

Related Questions