4

I have reCAPTCHA on 3 separate pages. They appear perfectly on the first load. But they disappear when I navigate to another page and then re-navigate back to that page (the one having the captcha) again. My application is a single-page application. I'm using next/link to navigation from one page to another page.

Here's my Contact Page:

import { useState, useEffect, useRef } from "react";
import { pattern } from "./pricing";
import styles from "./../styles/Contact.module.css";
import { post } from "../helper";
import { CONTACT_PAGE_ACTION_EVENT } from "../constant";
import ClipLoader from "react-spinners/ClipLoader";
import ReCAPTCHA from "react-google-recaptcha";


function Contact({captcha_site_key}) {
  const [formData, setFormData] = useState({
    firstName: "",
    email: "",
    emailError: "",
    phone: "",
    message: "",
    responseText: ""
  });
  const recaptchaRef = useRef(null);
  const [disabled, setDisabled] = useState(true);
  const [loading, setLoading] = useState(false)

  const formDataHandler = (name, value) => {
    setFormData((prev) => {
      if (name === "email") {
        return {
          ...prev,
          [name]: value.trimStart(),
          ["emailError"]: pattern.test(value.trimStart())
            ? ""
            : "That is Not a valid email",
        };
      } else {
        return {
          ...prev,
          [name]: value.trimStart(),
        };
      }
    });
  };

  useEffect(() => {
    setDisabled(
      !(
        formData.email.length > 0 &&
        formData.firstName.length > 0 &&
        formData.message.length > 0 &&
        formData.phone.length > 0 &&
        formData.emailError.length === 0
      )
    );
  }, [formData]);
  
  const sendData = async(e) => {
    e.preventDefault();
    setLoading(true)
    const token = await recaptchaRef.current.executeAsync();
    recaptchaRef.current.reset();
    const {emailError, responseText, ...data} = formData;

    // stripping off the html from string
    const secureData = {
      firstName: data.firstName.replace(/(<([^>]+)>)/gi, "").replace(/[`~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/]/gi, ''),
      email: data.email.replace(/(<([^>]+)>)/gi, "").replace(/[`~!#$%^&*()_|+\-=?;:'",<>\{\}\[\]\\\/]/gi, ''),
      phone: data.phone.replace(/(<([^>])>)/gi, "").replace(/[`~!@#$%^&*()_|\-=?;:'",.<>\{\}\[\]\\\/]/gi, ''),
      message: data.message.replace(/(<([^>]+)>)/gi, "").replace(/[`~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/]/gi, ''),
      token
    }
    const result = await  post(secureData, CONTACT_PAGE_ACTION_EVENT)

    if(result.status === 'success'){
      setFormData({
        firstName: "",
        email: "",
        emailError: "",
        phone: "",
        message: "",
        responseText: result?.status
      })
    }else {
      setFormData(prev => {
        return {...prev, ['responseText']: result?.status}
      })
    }
    setLoading(false)

  }
  return (
    <section className={styles.contact_container}>
      <ReCAPTCHA
        ref={recaptchaRef}
        size="invisible"
        sitekey={captcha_site_key}
      />
      <section className={styles.contact_section}>
        <form>
          <h3> Let&apos;s Talk </h3>
          <p> Fill out the form to send us a message </p>

          {/* FirstName */}
          <div className="form-floating mb-3">
            <input 
              className="form-control shadow-none border-dark rounded-0" 
              id="floatingInput" 
              value={formData.firstName}
              style={{ backgroundColor: loading ? '#f1f1f1' : 'white' }}
              disabled={loading}
              required
              type="text"
              name="firstName"
              placeholder="First Name*"
              onChange={(e) =>
                formDataHandler(e.target.name, e.target.value.trimStart())
              }
            />
            <label htmlFor="floatingInput" className="text-muted">First Name*</label>
          </div>

          {/* Email */}
          <div>
            <div className="form-floating">
              <input 
                className="form-control shadow-none border-dark rounded-0" 
                id="floatingInput" 
                value={formData.email}
                style={{ backgroundColor: loading ? '#f1f1f1' : 'white' }}
                disabled={loading}
                required
                type="email"
                name="email"
                placeholder="Email*"
                onChange={(e) =>
                  formDataHandler(e.target.name, e.target.value.trimStart())
                }
              />
              <label htmlFor="floatingInput" className="text-muted">Email*</label>
            </div>
            <p> {formData.emailError} </p>
          </div>
            
          {/* Phone Number */}
          <div className="form-floating mb-3">
            <input 
              className="form-control shadow-none border-dark rounded-0" 
              id="floatingInput" 
              value={formData.phone}
              required
              disabled={loading}
              style={{ backgroundColor: loading ? '#f1f1f1' : 'white' }}
              type="text"
              placeholder="Phone Number*"
              name="phone"
              onChange={(e) =>
                formDataHandler(e.target.name, e.target.value.trimStart())
              }
            />
            <label htmlFor="floatingInput" className="text-muted">Phone Number*</label>
          </div>

          {/* Phone Number */}
          <div className="form-floating mb-3">
            <input 
              className="form-control shadow-none border-dark rounded-0" 
              id="floatingInput" 
              value={formData.message}
              required
              type="text"
              disabled={loading}
              style={{ backgroundColor: loading ? '#f1f1f1' : 'white' }}
              placeholder="Message"
              name="message"
              onChange={(e) =>
                formDataHandler(e.target.name, e.target.value.trimStart())
              }
            />
            <label htmlFor="floatingInput" className="text-muted">Message*</label>
          </div>
          
          {/* Send Button */}
          <button
            disabled={disabled || loading}
            style={{
              display: 'flex',
              justifyContent: 'center',
              alignItems: 'center',
              opacity: disabled ? 0.5 : 1,
              backgroundColor:"#ff6600",
              cursor: disabled ? "auto" : "pointer",
              width: 150
            }}
            onClick={(e) => sendData(e)}
          >
            <span style={{marginRight: 5}}> Send </span> 
            <ClipLoader color={'white'} loading={loading} size={15} />
          </button>
          <p 
            style={{ 
              color: formData.responseText === 'error' ? 'red' : 'green',
              textAlign: 'center',
              height: 16,
              marginTop: 10
            }}
         > {formData.responseText} </p>
        </form>
      </section>
    </section>
  );
}

export async function getStaticProps() {
  return {
    props: {
      captcha_site_key: process.env.RECAPTCHA_SITE_KEY
    }
  }
}

export default Contact;

3 Answers 3

1

Does your app have the _document file matching Next's repository? It worked for me once I added the MyDocument class as shown below:

import Document from 'next/document'
import { ServerStyleSheet } from 'styled-components'

export default class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const sheet = new ServerStyleSheet()
    const originalRenderPage = ctx.renderPage

    try {
      ctx.renderPage = () =>
        originalRenderPage({
          enhanceApp: (App) => (props) =>
            sheet.collectStyles(<App {...props} />),
        })

      const initialProps = await Document.getInitialProps(ctx)
      return {
        ...initialProps,
        styles: (
          <>
            {initialProps.styles}
            {sheet.getStyleElement()}
          </>
        ),
      }
    } finally {
      sheet.seal()
    }
  }
}
1

Make sure you have reactStrictMode set to false in your next.config.js file. It should fix your problem.

1
  • It's not related to reactStrictMode. The issue persists even on production mode.
    – BiasInput
    Commented Nov 21, 2022 at 13:21
1

As suggested by anajavi at the official issue (https://github.com/dozoisch/react-google-recaptcha/issues/250), updating the package version to v3.0.0-alpha.1 solved the problem for me.

1
  • I encountered the issue in version 2.1.0. Based on the answer, updating to the latest version (currently 3.1.0) fixed the issue. Thanks Commented Oct 22, 2023 at 9:45

Not the answer you're looking for? Browse other questions tagged or ask your own question.