At a glance
- Component
- Client
- Backend
- None
- Submit
- fetch, JSON
- On error
- Keeps what was typed
- Endpoint
- Public, safe in the browser
The integration
This is a client component. The endpoint URL lives safely in the browser, like a mailto link. Drop it in as-is, or copy just the submit handler into your existing form and keep your own markup and styles.
import { useState, type FormEvent } from 'react';
type Status =
| { state: 'idle' | 'submitting' | 'success' }
| { state: 'error'; message: string };
export function ContactForm() {
const [status, setStatus] = useState<Status>({ state: 'idle' });
async function handleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
const form = event.currentTarget;
setStatus({ state: 'submitting' });
try {
const response = await fetch('https://mtform.co/f/your-form-key', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(Object.fromEntries(new FormData(form))),
});
const result = await response.json();
if (!response.ok) {
throw new Error(result.error?.message ?? 'Unable to send your message.');
}
form.reset();
setStatus({ state: 'success' });
} catch (error) {
// Entered values stay put; we only reset on success.
const message = error instanceof Error ? error.message : 'Unable to send your message.';
setStatus({ state: 'error', message });
}
}
return (
<form onSubmit={handleSubmit}>
<input name="name" placeholder="Name" required />
<input type="email" name="email" placeholder="Email" required />
<textarea name="message" placeholder="Message" required />
{/* Spam trap: hidden from people, tempting to bots. */}
<input
type="text"
name="_mtf_honeypot"
tabIndex={-1}
autoComplete="off"
aria-hidden="true"
style={{ position: 'absolute', left: '-9999px' }}
/>
<button type="submit" disabled={status.state === 'submitting'}>
{status.state === 'submitting' ? 'Sending…' : 'Send'}
</button>
{status.state === 'success' && <p role="status">Thanks. We got your message.</p>}
{status.state === 'error' && <p role="alert">{status.message}</p>}
</form>
);
}Step by step
- 1
Create the endpoint
Create a form in MakeTheForm to get a public endpoint URL and an inbox. The URL is an address, not a secret, so it’s fine in client-side React.
- 2
Wire the submit handler
In onSubmit, call preventDefault, then fetch() the endpoint with your form data as JSON. Disable the button while the request is in flight.
- 3
Handle success and error
Treat any 2xx as success and reset the form. On error, show the returned message and leave the entered values in place: never reset on failure.
- 4
Verify and ship
Click the link in the verification email once, then submit the form. It lands in your inbox and by email with the sender in Reply-To.
Common pitfalls
Resetting the form on error
form.reset() belongs only in the success branch. If you reset in a finally block, a failed send wipes everything the visitor typed. Keep values on error so they can retry without re-entering the whole message.
Reaching for a server you don’t need
A plain React app is client-side, and the endpoint accepts browser POSTs directly. Standing up a Node/Express relay just to forward the request adds a server to deploy and an email service to keep alive: for no gain.
Troubleshooting
| Symptom | What’s happening | Fix |
|---|---|---|
| CORS error | The request isn’t a simple JSON POST, or a wrapper added a disallowed header. | Send Content-Type: application/json with a plain fetch; the endpoint answers the browser’s CORS preflight. |
| button stuck disabled | The submitting state never resets because an error threw before it cleared. | Set state back to idle/error inside catch, not only on success, so the button re-enables. |
| 422 | The POST arrived with no fields at all (reserved _mtf_ fields do not count). | Give every input a name attribute: the browser never sends an input without one. With fetch, check the body is FormData or JSON built from the form, not empty. |
Frequently asked questions
How do I send a React form to email?
Point the form’s submit handler at a hosted form endpoint with fetch(). The endpoint receives the POST, filters spam, and emails you each submission, so you never write or host any mail-sending server code in React.
Does React have built-in form handling?
React manages form state and input in the browser, but has no built-in backend to receive or email submissions. You either build a server route yourself or POST to a form endpoint that handles delivery and storage.
Can I send email directly from React?
Not safely. Sending email needs a credential, and anything in a React bundle is readable by every visitor. Keep the email provider on a server, or POST the form to an endpoint that sends the email for you and needs no key in the browser.
Is it safe to put the form endpoint in React client code?
Yes. The public form key is a non-secret address meant for the browser, like a mailto link. Your destination email never appears in the page source, and there’s no API key to leak.
Other platforms
Keep going
Last reviewed by the MakeTheForm team.