using ReCAPTCHA v3 with useFormState and server actions
Answered
Birman posted this in #help-forum
BirmanOP
hello,
i am currently in the process of adding recaptcha to a next 14 project.
there is a contact form on the website using nodemailer to send an email.
i am trying to generate the token needed for verification when a form is submitted.
the form in question is using a server action to perform validation and error handling before an email is sent. this is where i would like to verify the token.
as i understand though, most npm packages and wrappers for recaptcha v3 all imply client side usage with inputs managed via setState. since i use formData and a server action, i haven't found yet how to generate a token on the client on submission of the form and sending it to the server action. most medium tutorials propose client side solutions like disabling a button but that kind of seems pointless to me, which is why i'm trying to do it inside the server action.
i have tried a couple of things:
- onSubmit alongside action to generate token and then submit with requestSubmit, didn't work
- wonky attempt at dynamic binding to a server action, didn't work
any help is appreciated.
code below in 5 minutes.
i am currently in the process of adding recaptcha to a next 14 project.
there is a contact form on the website using nodemailer to send an email.
i am trying to generate the token needed for verification when a form is submitted.
the form in question is using a server action to perform validation and error handling before an email is sent. this is where i would like to verify the token.
as i understand though, most npm packages and wrappers for recaptcha v3 all imply client side usage with inputs managed via setState. since i use formData and a server action, i haven't found yet how to generate a token on the client on submission of the form and sending it to the server action. most medium tutorials propose client side solutions like disabling a button but that kind of seems pointless to me, which is why i'm trying to do it inside the server action.
i have tried a couple of things:
- onSubmit alongside action to generate token and then submit with requestSubmit, didn't work
- wonky attempt at dynamic binding to a server action, didn't work
any help is appreciated.
Answered by Birman
ended up using route handlers instead. seems like server actions need some work still
9 Replies
BirmanOP
import { submitContact } from "@/lib/actions";
function Toasts({ data }) {...}
export default function Contact() {
const [toasts, setToasts] = useState([]);
const [form, submit] = useFormState(submitContact, null);
const { pending } = useFormStatus();
useEffect(() => {
if (form && !toasts.includes(form) && !form.expired) {
setToasts((prevState) => [...prevState, form]);
setTimeout(() => {
form.expired = true;
setToasts((prevState) =>
prevState.filter((toast) => toast.id !== form.id),
);
}, 5000);
}
}, [form, toasts]);
//TODO: generate token on submission and append to data sent to server
return (
<>
<Toasts data={toasts} />
<form className={styles.form} action={submit}>//...</form>
</>
);
};take a quick look at this and see if it works for you : https://github.com/snelsi/next-recaptcha-v3
BirmanOP
i have tried this, but it implies the use of useState, which i'm not using. i use a form server action and useFormState
Ok welp, I'm not sure if there is a purely server only way to do this :/
BirmanOP
ended up using route handlers instead. seems like server actions need some work still
Answer
Polar bear
@Birman - I'm in the same situation, would you happen to have any code snippets you could share that show how you solved this one?
@Polar bear <@333224383986270208> - I'm in the same situation, would you happen to have any code snippets you could share that show how you solved this one?
BirmanOP
yeah sure. i used a route handler and
Contact.jsx
/api/contact/route.js
useState instead though. i didn't succeed with useFormStatus and server actions.Contact.jsx
"use client";
import { useState } from "react";
export default function Contact() {
const [pending, setPending] = useState(false); //loading state
const [input, setInput] = useState("");
async function handleSubmit() {
e.preventDefault();
setPending(true);
await grecaptcha.ready(() => {
grecaptcha
.execute("site key", {action: "relevant name", })
.then(async (token) => {
try {
let res = await fetch("/api/contact", {
method: "POST",
body: JSON.stringify({
input: input,
token: token,
}),
});
res = await res.json();
} catch (e) {
console.error(e);
}
setPending(false);
});
});
}
return (
<form onSubmit={handleSubmit}>
<input
type="text"
name="input"
id="input"
onChange={(e) => setInput(e.target.value)}
/>
</form>
)
}/api/contact/route.js
export async function POST(request) {
const res = await request.json();
async function verifyCaptcha(token) {
const res = await fetch(
`https://www.google.com/recaptcha/api/siteverify?secret=${process.env.RECAPTCHA_SECRET_KEY}&response=${token}`,
{ method: "POST", cache: "no-store" },
);
const json = await res.json();
return json.score >= 0.5;
}
//captcha & form validation, send email, etc...
}Polar bear
That was incredibly helpful, thanks for sharing!