cover

Cómo usar recaptcha en React: useRecaptcha hook

author photo

Héctorbliss

@hectorbliss

Mira el video si prefieres:

En Fixtergeek.com lidiamos mucho con robots. 🤖

El web-scrapping está a la orden del día. 🈷

Quiero decir que todo el tiempo robots malignos entran a nuestro sitio web e intentan engañarnos registrando correos falsos para conseguir o forzar el acceso a la plataforma. 🖥️🔫🤖

Por eso implementamos Google recaptcha, específicamente la versión 3.

Recuerda que mi código va sin garantías, es código con fines educativos (aunque yo mismo lo uso en producción) úsalo bajo tu propio riesgo. 🤓

import { useCallback, useEffect, useRef, useState, type FormEvent, } from "react"; import { useFetcher } from "react-router"; import { useToast } from "~/hooks/useToaster"; declare global { interface Window { grecaptcha: { ready: (cb: () => void) => void; execute: ( siteKey: string, options?: { action: string } ) => Promise<string>; }; } } export type UseRecaptchaParams = { siteKey?: string; options?: { action: string }; }; export default function useRecaptcha(onSubmit: EV) { // Estados 💾 const siteKey = "perro", options = { action: "submit" }; const [isReady, setIsReady] = useState(false); const fetcher = useFetcher<{ success: boolean }>(); const { error } = useToast(); const eventRef = useRef<EV>(null); // Carga 🔋 useEffect(() => { if (!siteKey && typeof siteKey !== "string") return setIsReady(true); // noop ^for testing (su no hay key no rompe) const scriptURL = new URL("https://www.google.com/recaptcha/api.js"); scriptURL.searchParams.set("render", siteKey); const script = document.createElement("script"); script.src = scriptURL.toString(); script.async = true; script.defer = true; script.onload = () => { console.debug("recaptcha cargado"); window.grecaptcha.ready(() => { setIsReady(true); }); }; document.head.appendChild(script); return () => { setIsReady(false); console.debug("recaptcha eliminado"); document.head.removeChild(script); }; }, [siteKey]); // Se ejecuta al usuario accionar 👨🏻‍💻 const execute = useCallback(async () => { if (!siteKey) return; if (!isReady) throw new Error("Recaptcha no cargó"); return await window.grecaptcha.execute(siteKey, options); }, [siteKey, options, isReady]); // proxy para el form type EV = FormEvent<HTMLFormElement> | SubmitEvent; const handleSubmit = async (event: EV) => { event.preventDefault(); const token = (await execute()) as string; fetcher.submit( { intent: "recaptcha_verify_token", token }, { method: "POST", action: "/api/user" } ); eventRef.current = event; }; useEffect(() => { if (!fetcher.data) return; if (fetcher.data.success) { onSubmit(eventRef.current); } else { error({ text: "Lo sentimos, creemos que eres un robot. 🤖 Intenta de nuevo.", }); } }, [fetcher]); return { handleSubmit, isReady, }; }

Este hook corre en el cliente, también necesitas un endpoint para validar el token desde el servidor, pues necesitamos usar el secret que recaptcha nos proveyó.

export const action = async ({ request }: ActionFunctionArgs) => { const formData = await request.formData(); const intent = formData.get("intent"); if (intent === "recaptcha_verify_token") { const url = new URL("https://www.google.com/recaptcha/api/siteverify"); url.searchParams.set("secret", process.env.RECAPTCHA_SECRET as string); url.searchParams.set("response", formData.get("token") as string); return await fetch(url.toString(), { method: "POST" }); // {success} } return null; }

¡Espero te sea útil! Recuerda comprar mi curso de RRv7.

Abrazo. Bliss. 🤓

¡Nuevo curso!

Animaciones web con React + Motion 🧙🏻