google-react-recaptcha-v2
v1.0.3
Published
React component for Google reCAPTCHA
Maintainers
Readme
🛡️ google-react-recaptcha-v2
Componente React ligero y tipado para Google reCAPTCHA v2 (checkbox e invisible). Sigue Clean Architecture — domain / application / infrastructure / presentation — para que mantener, extender y testear la librería sea trivial.
✅ TypeScript estricto
✅ Funciona con React 17+ (testeado con React 18 y 19)
✅ Invisible, compacto y checkbox, configurable
✅ Soporta Trusted Types (?trustedtypes=true)
✅ Helper server-side para/recaptcha/api/siteverify
✅ 82 tests + smoke test anti-"npm roto" incluido
✅ Bundle pequeño: ~6 KB (ESM y CJS minificado con treeshake)
📦 Instalación
npm install google-react-recaptcha-v2
# o
pnpm add google-react-recaptcha-v2
# o
yarn add google-react-recaptcha-v2🚀 Uso básico
Invisible (recomendado para formularios)
import { useRef } from "react";
import { ReCaptchaV2, type ReCaptchaV2Ref } from "google-react-recaptcha-v2";
export function LoginForm() {
const recaptchaRef = useRef<ReCaptchaV2Ref>(null);
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const token = await recaptchaRef.current?.execute();
if (!token) return; // grecaptcha no estaba listo
// Envía `token` al backend para verificarlo.
};
return (
<form onSubmit={handleSubmit}>
<input name="email" type="email" required />
<input name="password" type="password" required />
<button type="submit">Entrar</button>
<ReCaptchaV2 ref={recaptchaRef} siteKey="YOUR_SITE_KEY" />
</form>
);
}Checkbox visible
<ReCaptchaV2
ref={recaptchaRef}
siteKey="YOUR_SITE_KEY"
size="normal"
theme="dark"
onSuccess={(token) => console.log("Listo para enviar:", token)}
onExpired={() => console.warn("El usuario tardó demasiado, debe revalidar")}
/>Modo desarrollo (isDevelop)
Si quieres probar sin configurar nada en Google, usa la test key oficial:
<ReCaptchaV2
ref={recaptchaRef}
isDevelop
// siteKey se ignora cuando isDevelop={true}
/>🧠 API pública
Componente <ReCaptchaV2>
| Prop | Tipo | Default | Descripción |
| ----------------- | ----------------------------------------------------- | ------------- | ------------------------------------------------------------ |
| siteKey | string | — | Requerido. Site key de Google reCAPTCHA v2. |
| onSuccess | (token: string) => void | — | Callback cuando el usuario resuelve el challenge. |
| onError | () => void | — | Callback si la API falla (red, configuración, etc.). |
| onExpired | () => void | — | Callback cuando el token expira antes de ser verificado. |
| size | "invisible" \| "normal" \| "compact" | "invisible" | Tamaño del widget. |
| theme | "light" \| "dark" | "light" | Tema visual. |
| badge | "bottomright" \| "bottomleft" \| "inline" | "bottomright" | Posición del badge cuando size="invisible". |
| tabindex | number | 0 | Tabindex del widget. |
| hl | string | — | Idioma BCP-47 (p.ej. "es", "en"). |
| isDevelop | boolean | false | Usa la site key oficial de pruebas de Google (siempre pasa). |
| trustedTypes | boolean | false | Carga la variante ?trustedtypes=true del script. |
| multiple | boolean | false | @deprecated. Cada <ReCaptchaV2> ya es independiente. |
Ref (ReCaptchaV2Ref)
| Método | Tipo | Descripción |
| --------------- | -------------------------- | ------------------------------------------------------------ |
| execute() | () => Promise<string> | Ejecuta el reto invisible y devuelve el token. |
| reset() | () => void | Resetea el widget y descarta el token actual. |
| getResponse() | () => string \| null | Devuelve el token actual del widget, o null si no hay. |
🔒 Verificación server-side (backend)
Google exige verificar el token en el backend antes de confiar en él. La librería incluye un helper con resultado discriminado:
// Node, Bun, Deno o cualquier Edge runtime con `fetch` global.
// (Node 18+ tiene fetch nativo.)
import { verifyRecaptchaV2Token } from "google-react-recaptcha-v2";
app.post("/api/login", async (req, res) => {
const result = await verifyRecaptchaV2Token({
token: req.body["g-recaptcha-response"],
secret: process.env.RECAPTCHA_SECRET!,
remoteIp: req.ip, // opcional pero recomendado
});
if (!result.success) {
// result.errorCodes puede ser:
// ["missing-token"], ["missing-secret"], ["network-error"],
// ["http-<status>"], ["invalid-json"], ["verification-failed"],
// o los códigos oficiales de Google ["invalid-input-response", ...]
return res.status(400).json({ error: result.errorCodes });
}
// result.success === true:
// { challengeTs, hostname, success: true }
// Tu backend decide qué hacer.
});🏗️ Arquitectura limpia (Clean Architecture)
| Capa | Responsabilidad | Carpeta |
| --------------- | --------------------------------------------------------------------- | ---------------------- |
| domain | Tipos y contratos del componente y ref. | libs/domain/ |
| application | Caso de uso (hook React) que orquesta el ciclo de vida. | libs/application/ |
| infrastructure| Carga de script, grecaptcha.render, helpers de red. | libs/infrastructure/ |
| presentation | Componente React fino que delega todo al hook. | libs/presentation/ |
| constants | Identificadores y URLs reutilizados. | libs/constants/ |
Reglas de capa (estrictas):
domainno importa de nadie más.applicationimporta dedomain+infrastructure.infrastructureimporta dedomain+constants.presentationimporta deapplication+domain.- Sólo
libs/index.tsdebe llegar al consumidor.
🧪 Tests y build
pnpm typecheck # tsc --noEmit (TS estricto)
pnpm test # vitest en modo watch
pnpm test:run # vitest run (CI)
pnpm test:coverage # reporte de coverage (v8)
pnpm test:npm-smoke # simula ser un consumidor (pack → install → tsc)
pnpm build # tsup → dist/{index.js, index.mjs, index.d.ts, index.d.mts}
pnpm prepublishOnly # typecheck + tests + build + smoke (encadenado)Tests incluidos (82)
| Capa | Archivo |
| --------------- | -------------------------------------------------------- |
| infrastructure| recaptchaV2Script.test.ts (12 tests) |
| infrastructure| reCaptchaV2Service.test.ts (14 tests) |
| infrastructure| verifyRecaptchaV2Token.test.ts (10 tests) |
| application | useReCaptchaV2.test.tsx (10 tests) |
| presentation | ReCaptchaV2.test.tsx (5 tests) |
| Anti-"npm roto" | types-export.test.ts (14 tests) + types-dist-dts.test.ts (7 tests) |
| Smoke del paquete| published-package.test.ts (10 tests) |
El test estrella
tests/types-dist-dts.test.ts parsea el dist/index.d.ts con el compilador real de TypeScript y verifica que cada export público está declarado. Si tsup rompe el bundle de tipos por cualquier razón futura, este test falla antes de que un usuario rompa su build.
scripts/npm-smoke.mjs empaqueta la librería, la instala en un proyecto limpio y compila un consumer.ts que importa cada export. Si los tipos están rotos en el paquete final, el smoke los cazaría.
🧰 API de bajo nivel (avanzado)
Si sólo necesitas parte de la funcionalidad sin el componente React:
import {
RecaptchaV2Service,
loadRecaptchaV2Script,
whenRecaptchaV2Ready,
installOnloadCallback,
GOOGLE_TEST_SITE_KEY,
} from "google-react-recaptcha-v2";
// Cargar script manualmente
installOnloadCallback(() => {
// se invoca cuando api.js termine de cargar
});
const result = loadRecaptchaV2Script({ hl: "es", trustedTypes: true });
// Esperar a grecaptcha.ready
const ready = await whenRecaptchaV2Ready(5000);
// Renderizar un widget programáticamente
const service = new RecaptchaV2Service();
const widgetId = service.renderWidget(container, props, {
onSuccess: (token) => {/* ... */},
});📌 Tips de uso
- Invisible es ideal para validaciones no intrusivas (formularios, acciones sensibles).
- Si necesitas retos visibles (checkbox), usa
size="normal"osize="compact". - Cambia el
props.siteKeyconisDevelop={true}en local para no necesitar claves reales. trustedTypes={true}cuando la app aplica CSP estricta.- El backend siempre debe verificar el token con
verifyRecaptchaV2Token.
🔒 Test key oficial de Google
Puedes usar esta site key en desarrollo; siempre resuelve el challenge con éxito:
import { GOOGLE_TEST_SITE_KEY } from "google-react-recaptcha-v2";
// "6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI"OJO: el helper integrado es isDevelop={true} en el componente, que ya hace esto por ti.
📜 Licencia
MIT © Andrés Felipe Hernández Aldana
💡 Créditos
Construido usando:
