Gatsby
React Forms with Gatsby
Gatsby sites are React apps that render to static HTML. A controlled React component with a fetch call lets you collect submissions without leaving the page, fully compatible with Gatsby's static export and CDN deployment.
01Create your form in Formward
Sign up at formward.eu, create a new form, and copy the endpoint URL (https://formward.eu/f/your-form-id).
02Create a ContactForm component
Add a new file src/components/ContactForm.jsx (or .tsx) and paste the component below. It uses controlled inputs and submits via fetch with Accept: application/json so Formward returns a JSON response instead of a redirect.
// src/components/ContactForm.jsx import React, { useState } from 'react'; export default function ContactForm() { const [fields, setFields] = useState({ name: '', email: '', message: '' }); const [status, setStatus] = useState('idle'); // idle | submitting | success | error function handleChange(e) { setFields({ ...fields, [e.target.name]: e.target.value }); } async function handleSubmit(e) { e.preventDefault(); setStatus('submitting'); try { const res = await fetch('https://formward.eu/f/your-form-id', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json', }, body: new URLSearchParams({ ...fields, _gotcha: '' }).toString(), }); if (!res.ok) throw new Error('Network response was not ok'); setStatus('success'); setFields({ name: '', email: '', message: '' }); } catch { setStatus('error'); } } if (status === 'success') { return <p>Thank you, your message has been sent!</p>; } return ( <form onSubmit={handleSubmit} style={{ display: 'flex', flexDirection: 'column', gap: 14, maxWidth: 480 }}> <label> Name <input type="text" name="name" value={fields.name} onChange={handleChange} required style={{ display: 'block', width: '100%', padding: '8px 12px', border: '1px solid #ccc', borderRadius: 4, fontSize: '1rem', marginTop: 4 }} /> </label> <label> Email <input type="email" name="email" value={fields.email} onChange={handleChange} required style={{ display: 'block', width: '100%', padding: '8px 12px', border: '1px solid #ccc', borderRadius: 4, fontSize: '1rem', marginTop: 4 }} /> </label> <label> Message <textarea name="message" value={fields.message} onChange={handleChange} rows={5} required style={{ display: 'block', width: '100%', padding: '8px 12px', border: '1px solid #ccc', borderRadius: 4, fontSize: '1rem', marginTop: 4, resize: 'vertical' }} /> </label> {/* honeypot: keep hidden */} <input type="text" name="_gotcha" style={{ display: 'none' }} tabIndex={-1} autoComplete="off" /> <button type="submit" disabled={status === 'submitting'} style={{ alignSelf: 'flex-start', padding: '10px 24px', background: '#000', color: '#fff', border: 'none', borderRadius: 4, fontSize: '1rem', cursor: 'pointer' }}> {status === 'submitting' ? 'Sending…' : 'Send'} </button> {status === 'error' && <p style={{ color: 'red' }}>Something went wrong. Please try again.</p>} </form> ); }03Use the component on a page
Import and render ContactForm on any Gatsby page, for example src/pages/contact.jsx. Gatsby will include the component in the static HTML it generates, and the fetch call runs in the browser at runtime.
// src/pages/contact.jsx import React from 'react'; import ContactForm from '../components/ContactForm'; export default function ContactPage() { return ( <main> <h1>Contact</h1> <ContactForm /> </main> ); }04Test and view submissions
Run gatsby develop, open http://localhost:8000/contact, and submit the form. The component shows a success message without a page reload. Check the Formward dashboard to confirm the submission arrived.