Search code examples
javascriptreactjsformsnext.js

Next.js/React Form with Cloudflare Turnstile unable to get response token from form


I'm new to Next.js/React, and I am trying to build out a simple contact form and protect it with Cloudflare Turnstile. Without Turnstile, I have it working with no issue. The form submits to the API and sends the email out.

Enviroment:

Next.js 13 using 'use client'

importing:

import { useState } from 'react';

import { Turnstile } from '@marsidev/react-turnstile';

However, now that I am trying to check the form submission with Turnstile, I can never get the input field containing the response token. I can inspect the form and see the input field with the response token filled out. Here is my code:

Form

      <form onSubmit={handleSubmit}>
        <div className="flex flex-col gap-4">
          <div className="form-control w-full max-w-md">
            <label className="label" htmlFor="name">
              <span className="label-text">Name</span>
            </label>
            <input
              type="text"
              id="name"
              placeholder="Your Name"
              required
              className="input w-full max-w-md"
              name="name"
              onChange={(e) => {
                setName(e.target.value);
              }}
            />
          </div>
          <div className="form-control w-full max-w-md">
            <label className="label" htmlFor="email">
              <span className="label-text">Email</span>
            </label>
            <input
              type="text"
              id="email"
              placeholder="Your Email"
              required
              className="input w-full max-w-md"
              name="email"
              onChange={(e) => {
                setEmail(e.target.value);
              }}
            />
          </div>
          <div className="form-control w-full max-w-md">
            <label className="label" htmlFor="subject">
              <span className="label-text">Subject</span>
            </label>
            <input
              type="text"
              id="subject"
              placeholder="Subject"
              required
              className="input w-full max-w-md"
              name="subject"
              onChange={(e) => {
                setSubject(e.target.value);
              }}
            />
          </div>
          <div className="form-control">
            <label className="label" htmlFor="message">
              <span className="label-text">Message</span>
            </label>
            <textarea
              className="textarea-bordered textarea h-48 w-full max-w-md"
              id="message"
              placeholder="Your message"
              required
              name="message"
              onChange={(e) => {
                setMessage(e.target.value);
              }}
            ></textarea>
          </div>
          <Turnstile siteKey={cfSiteKey} />
          <button type="submit" className="btn-primary btn w-full max-w-md">
            Submit
          </button>
        </div>
      </form>

Handler

  let cfSiteKey: string;

  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [subject, setSubject] = useState('');
  const [message, setMessage] = useState('');
  const [submitted, setSubmitted] = useState(false);

  if (process.env.NEXT_PUBLIC_CLOUDFLARE_TURNSTILE_SITE_KEY) {
    cfSiteKey= process.env.NEXT_PUBLIC_CLOUDFLARE_TURNSTILE_SITE_KEY;
  } else {
    throw new Error('Cloudflare Turnstile Secret Key Not Set');
  }
  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    const formData = new FormData();
    const token = formData.get('cf-turnstile-response');
    console.log(token);

    const data = {
      name,
      email,
      subject,
      message,
      token,
    };
    fetch('/api/contact', {
      method: 'POST',
      headers: {
        Accept: 'application/json, text/plain, */*',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(data),
    }).then((res) => {
      console.log('Response received');
      if (res.status === 200) {
        console.log('Response succeeded!');
        setSubmitted(true);
        setName('');
        setEmail('');
        setSubject('');
        setMessage('');
      }
    });
  };

Above, token always returns null when I submit the form. What am I missing or doing wrong here?


Solution

  • I guess I will answer my question.

    I had tried many things inside new FormData() but they did not work. So I left it blank and looked elsewhere in my code for the problem.

    The answer, along with correct typing is the following inside the handler:

      const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
        e.preventDefault();
        const formData = new FormData(e.currentTarget);
        const token = formData.get('cf-turnstile-response') as string;
        console.log(token);
    

    Now the token is available and sent correctly to the API.