Search code examples
vue.jsvuejs3cloudflare-turnstile

How to integrate vue-turnstile into a vue application?


I have a simple vue form where I utilize a dependency called vue-turnstile which in turn adds the captcha the captcha from cloudflare to my form. My issue is I get a strange error using it and I cannot for the reason figure out why this wouldnt work. My warning is as follows:

   [Vue warn]: Missing ref owner context. ref cannot be used on hoisted vnodes. A vnode with ref must be created inside the render function. 
  at <VueTurnstile ref="reCaptchaTurnstile" site-key="324324rsdxdfw3r4fd" modelValue=""  ... > 

And the error below shows this:

api.js?onload=cfTurnstileOnLoad&render=explicit:1 Uncaught (in promise) TurnstileError: [Cloudflare Turnstile] Invalid type for parameter "container", expected "string" or an implementation of "HTMLElement".

My component with the form is as follows which shows vue-turnstile integration at the end of the form:

<template>
  <div class="text-center contact-us-form">
    <h4 class="text-weight-bolder q-my-xs">GET A FREE QUOTE</h4>
    <div class="text-subtitle1 text-accent q-mb-xl">
      If you would like a quote or have any questions give us a call or send us
      an email using the contact form!
    </div>
    <q-form @submit="onSubmit" @reset="onReset">
      <div class="row q-col-gutter-md">
        <div class="col-6">
          <q-input
            filled
            v-model="firstName"
            label="First Name"
            hide-bottom-space
            lazy-rules="ondemand"
            :rules="[(val) => (val && val.length > 0) || 'First name required']"
          />
        </div>
        <div class="col-6">
          <q-input
            filled
            v-model="lastName"
            label="Last Name"
            hide-bottom-space
            lazy-rules="ondemand"
            :rules="[(val) => (val && val.length > 0) || 'Last name required']"
          />
        </div>
        <div class="col-12">
          <q-input
            filled
            v-model="phoneNumber"
            type="tel"
            mask="(###) ###-####"
            label="Phone Number"
            hide-bottom-space
            lazy-rules="ondemand"
            :rules="phoneNumberRules"
          />
        </div>
        <div class="col-12">
          <q-input
            filled
            v-model="emailAddress"
            type="email"
            label="Email"
            hide-bottom-space
            lazy-rules="ondemand"
            :rules="emailRules"
          />
        </div>
        <div class="col-12">
          <q-select
            filled
            v-model="typeOfService"
            :options="options"
            label="Type of Service"
            hide-bottom-space
            lazy-rules="ondemand"
            :rules="[
              (val) => (val && val.length > 0) || 'Type of service required',
            ]"
          />
        </div>
        <div class="col-12">
          <q-input
            v-model="message"
            filled
            clearable
            type="textarea"
            label="Subject"
            hide-bottom-space
            lazy-rules="ondemand"
            :rules="[(val) => (val && val.length > 0) || 'Message required']"
          />
        </div>
        <div class="col-12">
          <q-btn
            :loading="loading"
            color="primary"
            icon="send"
            label="Send"
            class="full-width q-pa-md"
            unelevated
            type="submit"
          >
            <template v-slot:loading>
              <q-spinner-gears class="q-ma-sm" /> Sending...
            </template>
          </q-btn>
        </div>
        <div class="col-12">
          <vue-turnstile
            ref="reCaptchaTurnstile"
            site-key="0x4AAAAAAAIGxTIQ6Z8JWGTo"
            v-model="token"
          />
        </div>
      </div>
    </q-form>
  </div>
</template>

<script setup>
import { ref } from 'vue';
import { useQuasar } from 'quasar';
import { api } from 'boot/axios';
import VueTurnstile from 'vue-turnstile';

//quasar variable
const $q = useQuasar();

//form inputs
const firstName = ref(null);
const lastName = ref(null);
const phoneNumber = ref(null);
const emailAddress = ref(null);
const typeOfService = ref(null);
const message = ref(null);
const token = ref('');
const reCaptchaTurnstile = ref(null); // we needed to define the ref for this so we could access reset()
const loading = ref(false);

// Drop down value for the services
const options = [
  'Chimney Repair',
  'Chimney Maintenance',
  'Chimney Inspection',
  'Other',
];

//form rules -- Added down here as reactive variables mainly due to complexity of their validation rules
const phoneNumberRules = ref([
  (v) => !!v || 'Phone number is required',
  (v) => v.length == 14 || 'Phone number must be valid',
]);
const emailRules = ref([
  (v) => !!v || 'E-mail is required',
  (v) =>
    /^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/.test(v) ||
    'E-mail must be valid',
]);

//methods
const onReset = () => {
  firstName.value = null;
  lastName.value = null;
  phoneNumber.value = null;
  emailAddress.value = null;
  typeOfService.value = null;
  message.value = null;
  token.value = '';
  reCaptchaTurnstile.value.reset();
};

const onSubmit = () => {
  //start widget spinner and turn of when complete
  loading.value = true;
  let jsonData = {
    FirstName: firstName.value,
    LastName: lastName.value,
    PhoneNumber: phoneNumber.value,
    EmailAddress: emailAddress.value,
    TypeOfService: typeOfService.value,
    Message: message.value,
    Token: token.value,
  };

  if (token.value != '') {
    api
      .post('/api/contactus', jsonData)
      .then((response) => {
        console.log(response);
        //reset the form
        onReset();

        loading.value = false;

        $q.notify({
          color: 'green-4',
          textColor: 'white',
          icon: 'cloud_done',
          message: 'Your message has been successfully sent!',
        });
      })
      .catch(() => {
        console.log('API request failed');
        loading.value = false;
      });
  }
};
</script>
<style lang="scss" scoped>
#cf-chl-widget-mpzku {
  width: 100% !important;
}

.contact-us-form {
  max-width: 450px;
  margin: auto;
}
</style>

I have tried deleting the package and reinstalling it. I cannot understand what this error even means. I really need to figure this out to stop spam and I have used this vue-turnstile in other apps that are still working so I am not sure why this wouldnt work for this app I am creating.


Solution

  • This is a common versioning issue and differences between yarn.lock, node module folders, and package.json. So run

    rm -rf node_modules yarn.lock
    yarn install