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?

4
  • it is usually only use async or defer not both at same time. Commented Feb 8, 2018 at 19:52
  • Corrected that, thanks. Commented Feb 8, 2018 at 19:54
  • Possible duplicate of Asynchronous Script Loading Callback Commented Feb 8, 2018 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. Commented Feb 8, 2018 at 20:01

1 Answer 1

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()
    }
  }
}
Sign up to request clarification or add additional context in comments.

3 Comments

It looks interesting, I am trying it now! What is the data parameter for in captchaReady: function(data)?
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) vuejs.org/v2/guide/computed.html#Computed-vs-Watched-Property
Ah, totally forgot that. It works and it is pretty ingenious. Marked as accepted. Cheers!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.