Skip to content

Instantly share code, notes, and snippets.

@petyappetrov
Last active January 27, 2021 05:59
Show Gist options
  • Save petyappetrov/104e2f5a2d9d9b7b21094e1766b76751 to your computer and use it in GitHub Desktop.
Save petyappetrov/104e2f5a2d9d9b7b21094e1766b76751 to your computer and use it in GitHub Desktop.
Google reCAPTCHA V2 invisible mode make in React Hooks
import { useCallback, useEffect, useRef } from "react";
const RECAPTCHA_SCRIPT_URL = "https://www.google.com/recaptcha/api.js";
const RECAPTCHA_SIZE = "invisible";
const RECAPTCHA_RESPONSE_KEY = "responseCallback";
const RECAPTCHA_EXPIRED_KEY = "expiredCallback";
const RECAPTCHA_ERROR_KEY = "onerrorCallback";
type RecapatchaOptions = {
siteKey: string;
};
export interface ReCAPTCHA {
execute(): Promise<void>;
reset(): () => void;
ready(fn: () => void): Promise<void>;
}
export const useScript = (src: string) => {
return useCallback(
() =>
new Promise<boolean>((resolve, reject) => {
const alreadyExistScript = document.querySelector(
`script[src="${src}"]`
);
if (alreadyExistScript) {
resolve(true);
} else {
const script = document.createElement("script");
script.src = src;
script.async = true;
script.defer = true;
script.onload = () => resolve(true);
script.onerror = () => reject(new Error("Error loading script"));
document.body.appendChild(script);
}
}),
[src]
);
};
export const useRecaptcha = (options: RecapatchaOptions) => {
const loadScript = useScript(RECAPTCHA_SCRIPT_URL);
const timerRef = useRef<NodeJS.Timeout | null>(null);
const isAvailable = (): boolean =>
Boolean(
window &&
window.grecaptcha &&
window.grecaptcha.ready &&
window.grecaptcha.execute
);
const checkBadge = useCallback(
() =>
new Promise<boolean>((resolve) => {
const alreadyExistBadge = document.querySelector(".g-recaptcha");
if (alreadyExistBadge) {
resolve(true);
} else {
const badge = document.createElement("div");
badge.setAttribute("data-sitekey", options.siteKey);
badge.setAttribute("data-size", RECAPTCHA_SIZE);
badge.setAttribute("data-callback", RECAPTCHA_RESPONSE_KEY);
badge.setAttribute("data-expired-callback", RECAPTCHA_EXPIRED_KEY);
badge.setAttribute("data-error-callback", RECAPTCHA_ERROR_KEY);
badge.classList.add("g-recaptcha");
document.body.appendChild(badge);
resolve(true);
}
}),
[options.siteKey]
);
const checkAvailable = useCallback(async (callback) => {
if (isAvailable()) {
if (timerRef.current) {
clearInterval(timerRef.current);
}
await window.grecaptcha.ready(() => {});
if (typeof callback === "function") {
callback();
}
} else {
timerRef.current = setInterval(() => checkAvailable(callback), 500);
}
}, []);
const prepareRecaptcha = useCallback(
() =>
new Promise<boolean>(async (resolve, reject) => {
try {
await checkBadge();
await loadScript();
await checkAvailable(resolve);
} catch (error) {
reject(error);
}
}),
[checkAvailable, checkBadge, loadScript]
);
const execute = useCallback(() => {
return new Promise<string>(async (resolve, reject) => {
window[RECAPTCHA_ERROR_KEY] = reject;
window[RECAPTCHA_EXPIRED_KEY] = reject;
window[RECAPTCHA_RESPONSE_KEY] = (token: string) => {
resolve(token);
if (token) {
setTimeout(() => window.grecaptcha.reset(), 500);
}
};
try {
await prepareRecaptcha();
} catch (error) {
reject(error);
}
if (isAvailable()) {
window.grecaptcha.execute();
}
});
}, [prepareRecaptcha]);
useEffect(() => {
return () => {
if (timerRef.current) {
clearInterval(timerRef.current);
}
};
}, []);
return execute;
};
declare global {
interface Window {
[RECAPTCHA_RESPONSE_KEY]?: (token: string) => void;
[RECAPTCHA_ERROR_KEY]?: () => void;
[RECAPTCHA_EXPIRED_KEY]?: () => void;
grecaptcha: ReCAPTCHA;
}
}
@petyappetrov
Copy link
Author

petyappetrov commented Jan 27, 2021

How to use:

const Component: React.FC = () => {
  const loadRecaptchaToken = useRecaptcha({ siteKey: "YOUR_PUBLIC_KEY" });

  const onSubmit = async () => {
    const token = await loadRecaptchaToken();
    console.log(token);
  };

  return (
    <div>
      <button onClick={onSubmit}>Submit</button>
    </div>
  );
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment