9

I want to render a recaptcha after a Vue.js component has mounted. It works on normal load and reload, but when I navigate away to a different url and click the browser back button it throws an error.

Here is my setup:

  • I am loading the api script at the bottom of the page:

    <script src='https://www.google.com/recaptcha/api.js' async></script>

  • on that page I render a globally registered component called Contact.vue that contains a local component called Recaptcha.vue:

    <app-recaptcha @recaptchaResponse="updateRecaptchaResponse"></app-recaptcha>

The code for Recaptcha.vue looks like this:

<template>
    <div id="app-recaptcha">
        <div :id="placeholderId"></div>
    </div>
</template>


<script>
    export default {
        data() {
            return {
                sitekey: 'key_here',
                placeholderId: 'grecaptcha',
                widgetId: null
            }
        },


        mounted() {
            this.$nextTick(function () {
                this.render();
            });
        },


        methods: {
            render() {
                this.widgetId = window.grecaptcha.render(this.placeholderId, {
                    sitekey: this.sitekey,
                    callback: (response) => {
                        this.$emit('recaptchaResponse', response);
                    }
                });
            },

            reset() {
                window.grecaptcha.reset(this.widgetId);
            }
        }
    }
</script>


<style>...</style>

On normal page load/ reload this.render() is executed normally. However, when I navigate to another url and return via the back button I get: Error in nextTick: "TypeError: window.grecaptcha.render is not a function".

I tried to:

  • set a variable on the onload event of the api script:

    <script src='...' onload="window.script = { recaptcha: 'ready' }" async></script>

  • then add it as a property in data() of Recaptcha.vue:

    ready: window.script.recaptcha

  • next, I added a watcher on the ready property and within that watcher I tried to run this.render()

No success, the error is still there. I think that even in the normal load/ reload situation I am simply "lucky" that the api script loads before the component gets mounted, and that placing this.render() inside the mounted() hook isn't helpful.

Do you know how can I signal Recaptcha.vue that the external script has finished loading, and only then render the recaptcha?

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Mihai
  • 2,807
  • 4
  • 28
  • 53
  • it is usually only use `async` or `defer` not both at same time. –  Feb 08 '18 at 19:52
  • Corrected that, thanks. – Mihai Feb 08 '18 at 19:54
  • Possible duplicate of [Asynchronous Script Loading Callback](https://stackoverflow.com/questions/12820953/asynchronous-script-loading-callback) –  Feb 08 '18 at 19:57
  • Thanks for your suggestion, but that doesn't really answer my question. I would prefer a simpler way to just signal the component that the script has loaded. – Mihai Feb 08 '18 at 20:01

1 Answers1

11

okay, here's a theory: add data properties

captchaReady: false, 
checkingInterval: null,

created

let localThis = this 
this.checkingInterval = setInterval(function(){
  if (window.grecaptcha) {
    localThis.captchaReady = true
  }
}, 500) //or whatever interval you want to check 

Then

watch: {
  captchaReady: function(data) {
    if (data) { 
       clearInterval(this.checkingInterval) 
       this.render()
    }
  }
}
LShapz
  • 1,738
  • 11
  • 19
  • It looks interesting, I am trying it now! What is the `data` parameter for in `captchaReady: function(data)`? – Mihai Feb 08 '18 at 21:27
  • watch functions take a parameter that is the value of the property being watched. In the (Vue docs they call it `val`) - so `if (data)` means `if (this.captchaReady === true)` https://vuejs.org/v2/guide/computed.html#Computed-vs-Watched-Property – LShapz Feb 08 '18 at 21:36
  • 1
    Ah, totally forgot that. It works and it is pretty ingenious. Marked as accepted. Cheers! – Mihai Feb 08 '18 at 21:38