reactjsnext.jsrecaptchareact-modal

Cant Reset ReCAPTCHA When React Modal Gets Closed by the User and Opened Again in Next.js


I have a react-modal form with a captcha in it. When the user open up the modal the captcha shows up just above the Report Job button. No problems there. but let say the user closes the modal before even submitting or escaped the modal. so in that case when the user opens it again probably the ReCAPTCHA must show up; but it doesnt. And in another case even if the user submits the form and then opens up the modal again in that case too, the ReCAPTCHA doesnt shows up. But if i refresh the website and then try to open up the modal in that case it works fine. but no one is going to refresh everytime to see the captcha.

"use client";
import { useState, useRef } from "react";
import Modal from "react-modal";
import ReCAPTCHA from "react-google-recaptcha";

Modal.setAppElement("#modal");

export default function ReportJobForm() {
  const [isModalOpen, setIsModalOpen] = useState(false);
  const [inValidCaptcha, setInValidCaptcha] = useState(true);

  const recaptchaRef = useRef<ReCAPTCHA>(null);
  const openModal = () => setIsModalOpen(true);

  const closeModal = () => {
    setIsModalOpen(false);
    setInValidCaptcha(true);
    console.log("recaptchaRef", recaptchaRef);

    if (recaptchaRef.current) {
      recaptchaRef.current.reset();
    }
  };

  const [formData, setFormData] = useState({
    name: "",
    email: "",
    reason: "",
    message: "",
  });

  const handleCaptchaOnChange = (value: string | null) => {
    console.log("ReCAPTCHA value:", value);
    if (value) {
      console.log(value);
      setInValidCaptcha(false);
    }
  };

  const handleInputChange = (e: any) => {
    const { name, value } = e.target;
    setFormData((prevFormData) => {
      return {
        ...prevFormData,
        [name]: value,
      };
    });
  };

  const handleFormSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    closeModal();
    recaptchaRef.current?.reset();
    console.log(formData);
    setInValidCaptcha(true);
  };

  return (
    <>
      <button className="cursor-pointer" onClick={() => openModal()}>
        Report Job
      </button>
      <Modal
        isOpen={isModalOpen}
        onRequestClose={closeModal}
        overlayClassName="bg-[rgba(0,0,0,.4)] flex justify-center items-center fixed top-0 left-0 h-screen w-screen"
        className="w-3/10 bg-white rounded-xl p-6"
      >
        <h1 className="text-xl font-bold mb-4">Report job</h1>
        <form onSubmit={handleFormSubmit}>
          <div className="mb-4">
            <label
              className="block text-gray-700 font-bold mb-2"
              htmlFor="name"
            >
              Name*
            </label>
            <input
              className="appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
              id="name"
              name="name"
              type="text"
              value={formData.name}
              onChange={handleInputChange}
              required
            />
          </div>
          <div className="mb-4">
            <label
              className="block text-gray-700 font-bold mb-2"
              htmlFor="email"
            >
              Email*
            </label>
            <input
              className="appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
              id="email"
              name="email"
              type="email"
              value={formData.email}
              onChange={handleInputChange}
              required
            />
          </div>
          <div className="mb-4">
            <label
              className="block text-gray-700 font-bold mb-2"
              htmlFor="reason"
            >
              Reason
            </label>
            <div className="relative">
              <select
                className=" border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
                id="reason"
                name="reason"
                value={formData.reason}
                onChange={handleInputChange}
                required
              >
                <option value="">Please select a reason</option>
                <option value="incorrect data">Incorrect data</option>
                <option value="job expired">Job expired</option>
                <option value="job not found">Job not found</option>
              </select>
            </div>
          </div>
          <div className="mb-4">
            <label
              className="block text-gray-700 font-bold mb-2"
              htmlFor="message"
            >
              Message*
            </label>
            <textarea
              className="appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
              id="message"
              name="message"
              value={formData.message}
              onChange={handleInputChange}
              required
            />
          </div>
          <ReCAPTCHA
            sitekey="Key"
            onChange={handleCaptchaOnChange}
            ref={recaptchaRef}
          />
          <button
            disabled={inValidCaptcha}
            className="bg-red-500 w-full
            hover:bg-red-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
            type="submit"
          >
            Report Job
          </button>
        </form>
      </Modal>
    </>
  );
}

I tried refering to a youtube video for reseting the ReCAPTCHA which it used useRef, the same solution were applied to mine too as you can see in the code. but it didnt work at all. When i tried to console.log the recaptchaRef.current it gives me null. I also tried referring to this stackoverflow questions but no luck. either i am not understanding it right or something wrong with my implementions. I am a begineer in nextjs and react-google-recaptcha please help.


Solution

  • I think I have found the source of the issue.

    Unfortunately, the issue is being caused by the react-modal package that is used. It is being described here https://github.com/reactjs/react-modal/issues/808 . And it seems to be caused by React v18 and strict mode. The problem seems to be when the modal is closed then a reference to it is still being held somewhere in memory.

    It is described in a bit more detail by the post here https://github.com/reactjs/react-modal/issues/808#issuecomment-1446289359 . However I believe that Google Recaptcha thinks that the previous modal element still exists because it is being held in memory rather than being garbage collected.

    So how React v18, when used in strict mode, handles garbage collection of objects. There is still a reference to the unmounted object being kept in memory somewhere and it is confusing Recaptcha.

    When I removed reactStrictMode: true, from the /next.config.js file, and restarted the development server environment everything seems to work.

    But as you know we don't want that.so without removing it just run npm run build && npm start in build mode it will work fine.

    It was a very strange issue. I have not seen anything like it before which made debugging difficult.