Next.js 表单 reCAPTCHA returns window 未定义 - react-hook-recaptcha

Next.js form reCAPTCHA returns window of undefined - react-hook-recaptcha

我正在尝试结合使用 react-hook-form 和 react-hook-recaptcha 来实施 reCAPTCHA 不确定是什么原因,但我收到以下关于 window 的错误,说它未定义:

ReferenceError: window is not defined
> 33 |   const { recaptchaLoaded, recaptchaWidget } = useRecaptcha({

代码:

import React, { useState } from "react";
import { useRecaptcha } from "react-hook-recaptcha";
import { useForm, SubmitHandler } from "react-hook-form";

import { urlFor } from "@lib/sanity";
import { dateFormat } from "@lib/helpers";
import { PortableText } from "@portabletext/react";

import { portableTextComponents } from "@components/portable-text";
import { FormError, FormSuccess } from "@components/Form";

import { ArticleProps, Comment } from "types/article";

const sitekey = "6Ld-*********"; // change to your site key
const containerId = "recaptcha"; // this id can be customized

declare global {
  interface Window {
    grecaptcha: any;
  }
}

const ArticleSingle = ({ page }: ArticleProps) => {
  const [submitted, setSubmitted] = useState(false);

  const { _id, body, featuredImage, title, author, publishedAt, comments } =
    page;

  const successCallback = (response: any) => {
    console.log("YUP");
  };

  const { recaptchaLoaded, recaptchaWidget } = useRecaptcha({
    containerId,
    successCallback,
    sitekey,
    size: "invisible",
  });

  const executeCaptcha = () => {
    if (recaptchaWidget !== null) {
      window.grecaptcha.reset(recaptchaWidget);
      window.grecaptcha.execute(recaptchaWidget);
    }
  };

  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<Comment>();

  const onSubmit: SubmitHandler<Comment> = async (e, data) => {
    e.preventDefault();

    executeCaptcha();

    fetch("/api/create-comment", {
      method: "POST",
      body: JSON.stringify(data),
    })
      .then(() => {
        console.log(data);
        setSubmitted(true);
      })
      .catch((error) => {
        console.log(error);
        setSubmitted(false);
      });
  };

  return (
    <div id="article">
      <div className="relative mb-4 md:mb-0" style={{ height: "45rem" }}>
        <div
          className="absolute bottom-0 left-0 z-10 w-full h-full"
          style={{
            backgroundImage:
              "linear-gradient(180deg,transparent,rgba(0,0,0,.75))",
          }}
        ></div>

        {featuredImage && featuredImage.asset && (
          <img
            alt={featuredImage?.alt || ""}
            src={urlFor(featuredImage).quality(85).url()}
            className="absolute top-0 left-0 z-0 object-cover w-full h-full"
          />
        )}

        <div className="absolute left-0 right-0 z-20 max-w-screen-lg p-4 mx-auto bottom-2">
          {title && (
            <h1 className="text-4xl font-semibold leading-tight text-gray-100 md:text-5xl lg:text-7xl">
              {title}
            </h1>
          )}

          <div className="flex gap-5 mt-5 place-items-center">
            {author?.featuredImage && (
              <img
                className="object-cover object-center w-12 h-12 border-2 border-white rounded-full shadow-lg md:w-16 md:h-16"
                alt={author?.featuredImage?.alt || ""}
                src={urlFor(author.featuredImage).quality(85).url()!}
              />
            )}

            <div>
              {author && (
                <p className="font-semibold text-gray-200 sm:text-md md:text-xl">
                  {author.name}
                </p>
              )}

              {publishedAt && (
                <time className="font-semibold text-bubblegum sm:text-md md:text-xl">
                  {dateFormat(publishedAt)}
                </time>
              )}
            </div>
          </div>
        </div>
      </div>
      <div className="max-w-screen-lg px-4 pb-8 mx-auto mt-12 text-navyBlue text-md md:text-xl md:leading-relaxed">
        <PortableText value={body} components={portableTextComponents} />

        {/* Comment Form */}
        <div id="article-comments">
          <hr className="my-5 mb-10 border border-yellow-500" />

          {submitted ? (
            <FormSuccess
              title="Thank you for submitting your comment!"
              message="Once it has been approved, it will appear below!"
            />
          ) : (
            <>
              <h3 className="text-md text-bubblegum">Enjoyed this article?</h3>
              <h4 className="text-3xl font-bold">Leave a comment below!</h4>
              <hr className="py-3 mt-2" />

              <form
                className="flex flex-col pb-5 mx-auto"
                onSubmit={handleSubmit(onSubmit)}
              >
                <input
                  {...register("_id")}
                  type="hidden"
                  name="_id"
                  value={_id}
                />

                <input
                  {...register("name", {
                    required: "The Name Field is required",
                  })}
                  className="block w-full px-3 py-2 mt-4 border rounded shadow outline-none form-input ring-bubblegum focus:ring"
                  placeholder="Name"
                  type="text"
                />

                {errors.name && <FormError message={errors.name.message} />}

                <input
                  {...register("email", {
                    required: "The Email Field is required",
                    pattern: {
                      value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i,
                      message: "Enter a valid e-mail address",
                    },
                  })}
                  className="block w-full px-3 py-2 mt-4 border rounded shadow outline-none form-input ring-bubblegum focus:ring"
                  placeholder="Email"
                  type="email"
                />

                {errors.email && <FormError message={errors.email.message} />}

                <textarea
                  {...register("comment", {
                    required: "The Comment Field is required",
                    minLength: {
                      value: 40,
                      message: "A Min of 40 characters is required",
                    },
                  })}
                  className="block w-full px-3 py-2 mt-4 border rounded shadow outline-none resize-none form-textarea ring-bubblegum focus:ring"
                  placeholder="Comment"
                  rows={7}
                />

                {errors.comment && (
                  <FormError message={errors.comment.message} />
                )}

                <input
                  disabled={!recaptchaLoaded}
                  type="submit"
                  className="flex-none w-32 px-4 py-2 mt-6 text-xs font-bold text-center uppercase duration-300 ease-in-out bg-transparent border-2 rounded-full cursor-pointer text-navyBlue border-navyBlue hover:bg-navyBlue hover:text-bubblegum"
                ></input>
              </form>

              {/* Comments */}
              {comments.length > 0 && (
                <div className="flex flex-col my-10">
                  <h3 className="text-4xl">Comments</h3>
                  <hr className="mt-2 mb-5 border-2 border-yellow-500" />
                  {comments.map(({ name, _id, _createdAt, comment }) => (
                    <div
                      className="px-4 py-6 bg-white rounded-sm shadow-md"
                      key={_id}
                    >
                      <p>
                        <time className="block text-sm font-bold">
                          {dateFormat(_createdAt)}{" "}
                        </time>
                        <span className="text-bubblegum">{name}</span> :
                        <span className="pl-2">{comment}</span>
                      </p>
                    </div>
                  ))}
                </div>
              )}
            </>
          )}
        </div>
      </div>
    </div>
  );
};

export default ArticleSingle;

Next.js有一个server-side环境,window对象只存在于浏览器中。可以查看哪个环境码是运行这个:

const isServerSide = typeof window === 'undefined'

因此您的代码可能如下所示:

const executeCaptcha = () => {
  if (recaptchaWidget !== null && typeof window !== 'undefined') {
    window.grecaptcha.reset(recaptchaWidget);
    window.grecaptcha.execute(recaptchaWidget);
  }

};