
This guide will teach you how to send form data to any API endpoint.
In case you're starting a brand new project, you'll need some initial steps. One of the most straightforward ways is to use the create-react-app package. To start:
npm install create-react-app --globalmkdir ~/react-project && cd ~/react-projectnpx create-react-app .npm startUse your favorite code editor to work with files in ~/react-project/src. You will be able to make a contact form there.
Create a new file called UseForm.js in the src folder. The hook will intercept regular form submit and will send JSON data to the API endpoint.
1import { useState } from "react";
2
3function useForm({ additionalData }) {
4 const [status, setStatus] = useState('');
5 const [message, setMessage] = useState('');
6
7 const handleSubmit = (e) => {
8 e.preventDefault();
9 setStatus('loading');
10 setMessage('');
11
12 const finalFormEndpoint = e.target.action;
13 const data = Array.from(e.target.elements)
14 .filter((input) => input.name)
15 .reduce((obj, input) => Object.assign(obj, { [input.name]: input.value }), {});
16
17 if (additionalData) {
18 Object.assign(data, additionalData);
19 }
20
21 fetch(finalFormEndpoint, {
22 method: 'POST',
23 headers: {
24 Accept: 'application/json',
25 'Content-Type': 'application/json',
26 },
27 body: JSON.stringify(data),
28 })
29 .then((response) => {
30 if (response.status !== 200) {
31 throw new Error(response.statusText);
32 }
33
34 return response.json();
35 })
36 .then(() => {
37 setMessage("We'll be in touch soon.");
38 setStatus('success');
39 })
40 .catch((err) => {
41 setMessage(err.toString());
42 setStatus('error');
43 });
44 };
45
46 return { handleSubmit, status, message };
47}
48
49export default useForm;Create a new file called Form.js in the src folder. You can use any fields and any framework for styling it. For now, we're staying with the standard "Name," "Email," and "Message" for the simple contact form. We're also going to use TailwindCSS to make it beautiful, but you can use your own custom CSS code too.
1import useForm from "./UseForm";
2
3const FORM_ENDPOINT = "https://herotofu.com/start"; // TODO - update to the correct endpoint
4
5const Form = () => {
6 const additionalData = {
7 sent: new Date().toISOString(),
8 };
9
10 const { handleSubmit, status, message } = useForm({
11 additionalData,
12 });
13
14 if (status === "success") {
15 return (
16 <>
17 <div className="text-2xl">Thank you!</div>
18 <div className="text-md">{message}</div>
19 </>
20 );
21 }
22
23 if (status === "error") {
24 return (
25 <>
26 <div className="text-2xl">Something bad happened!</div>
27 <div className="text-md">{message}</div>
28 </>
29 );
30 }
31
32 return (
33 <form
34 action={FORM_ENDPOINT}
35 onSubmit={handleSubmit}
36 method="POST"
37 >
38 <div className="pt-0 mb-3">
39 <input
40 type="text"
41 placeholder="Your name"
42 name="name"
43 className="focus:outline-none focus:ring relative w-full px-3 py-3 text-sm text-gray-600 placeholder-gray-400 bg-white border-0 rounded shadow outline-none"
44 required
45 />
46 </div>
47 <div className="pt-0 mb-3">
48 <input
49 type="email"
50 placeholder="Email"
51 name="email"
52 className="focus:outline-none focus:ring relative w-full px-3 py-3 text-sm text-gray-600 placeholder-gray-400 bg-white border-0 rounded shadow outline-none"
53 required
54 />
55 </div>
56 <div className="pt-0 mb-3">
57 <textarea
58 placeholder="Your message"
59 name="message"
60 className="focus:outline-none focus:ring relative w-full px-3 py-3 text-sm text-gray-600 placeholder-gray-400 bg-white border-0 rounded shadow outline-none"
61 required
62 />
63 </div>
64 {status !== "loading" && (
65 <div className="pt-0 mb-3">
66 <button
67 className="active:bg-blue-600 hover:shadow-lg focus:outline-none px-6 py-3 mb-1 mr-1 text-sm font-bold text-white uppercase transition-all duration-150 ease-linear bg-blue-500 rounded shadow outline-none"
68 type="submit"
69 >
70 Send a message
71 </button>
72 </div>
73 )}
74 </form>
75 );
76};
77
78export default Form;Open App.js in your src folder, add contact form component, and enable TailwindCSS. If it's an existing project, open the file where the contact form should appear. You need to:
1import logo from "./logo.svg";
2import "./App.css";
3import { useEffect } from "react";
4import Form from "./Form";
5
6function App() {
7 // You can skip useEffect if you're not using TailwindCSS
8 // Otherwise, for the production usage refer to https://tailwindcss.com/docs/installation
9 useEffect(() => {
10 if (document) {
11 const stylesheet = document.createElement("link");
12 stylesheet.rel = "stylesheet";
13 stylesheet.href = "https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css";
14
15 document.head.appendChild(stylesheet);
16 }
17 }, []);
18
19 return (
20 <div className="App">
21 <header className="App-header">
22 <img src={logo} className="App-logo" alt="logo" />
23 <div className="py-6">
24 <Form />
25 </div>
26 </header>
27 </div>
28 );
29}
30
31export default App;If you're using HeroTofu forms backend it's easy to handle spam bots too. You have to check whenever the API returned 422 status code and redirect visitor to the captcha error page. This is done by modifying your hook around the line #34:
1import { useState } from "react";
2
3function useForm({ additionalData }) {
4 const [status, setStatus] = useState("");
5 const [message, setMessage] = useState("");
6
7 const handleSubmit = (e) => {
8 e.preventDefault();
9 setStatus('loading');
10 setMessage('');
11
12 const finalFormEndpoint = e.target.action;
13 const data = Array.from(e.target.elements)
14 .filter((input) => input.name)
15 .reduce((obj, input) => Object.assign(obj, { [input.name]: input.value }), {});
16
17 if (additionalData) {
18 Object.assign(data, additionalData);
19 }
20
21 fetch(finalFormEndpoint, {
22 method: 'POST',
23 headers: {
24 Accept: 'application/json',
25 'Content-Type': 'application/json',
26 },
27 body: JSON.stringify(data),
28 })
29 .then((response) => {
30 // It's likely a spam/bot request, so bypass it to validate via captcha
31 if (response.status === 422) {
32 Object.keys(additionalData).forEach((key) => {
33 const el = document.createElement('input');
34 el.type = 'hidden';
35 el.name = key;
36 el.value = additionalData[key];
37
38 e.target.appendChild(el);
39 });
40
41 e.target.setAttribute('target', '_blank');
42 e.target.submit();
43 throw new Error('Please finish the captcha challenge');
44 }
45
46 if (response.status !== 200) {
47 throw new Error(response.statusText);
48 }
49
50 return response.json();
51 })
52 .then(() => {
53 setMessage("We'll be in touch soon.");
54 setStatus('success');
55 })
56 .catch((err) => {
57 setMessage(err.toString());
58 setStatus('error');
59 });
60 };
61
62 return { handleSubmit, status, message };
63}
64
65export default useForm;See the React contact form guide for a complete guide how to implement HeroTofu forms backend.