React Integration
Build forms in React with full control over state, validation, and UX.
Testing Locally
To test from localhost, register your IP in form settings:
Optional: For extra security, add a Sandbox API Key to your .env.local:
bash
# .env.local (do not commit to git)
NEXT_PUBLIC_ATLAS_KEY=sk_sandbox_xyz123...Then include it in your fetch requests:
javascript
const response = await fetch(`https://atlasforms.app/f/${formId}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Atlas-Key': process.env.NEXT_PUBLIC_ATLAS_KEY, // Optional
},
body: JSON.stringify(data),
});Note: When you go Live, localhost will be blocked. Deploy to a real domain.
Basic Hook
javascript
import { useState } from 'react';
function useAtlasForm(formId) {
const [status, setStatus] = useState('idle'); // idle | loading | success | error
const [error, setError] = useState(null);
const [data, setData] = useState(null);
const submit = async (formData) => {
setStatus('loading');
setError(null);
try {
const response = await fetch(`https://atlasforms.app/f/${formId}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData),
});
const result = await response.json();
if (!response.ok) {
throw new Error(result.error || 'Submission failed');
}
setData(result);
setStatus('success');
return result;
} catch (err) {
setError(err.message);
setStatus('error');
throw err;
}
};
const reset = () => {
setStatus('idle');
setError(null);
setData(null);
};
return { submit, status, error, data, reset };
}Contact Form Component
javascript
import { useState } from 'react';
function ContactForm() {
const [formData, setFormData] = useState({
name: '',
email: '',
message: '',
});
const { submit, status, error, reset } = useAtlasForm('YOUR_FORM_ID');
const handleChange = (e) => {
setFormData((prev) => ({
...prev,
[e.target.name]: e.target.value,
}));
};
const handleSubmit = async (e) => {
e.preventDefault();
try {
await submit(formData);
setFormData({ name: '', email: '', message: '' });
} catch {
// Error handled by hook
}
};
if (status === 'success') {
return (
<div className="success">
<h2>Thank you!</h2>
<p>We'll be in touch soon.</p>
<button onClick={reset}>Send another message</button>
</div>
);
}
return (
<form onSubmit={handleSubmit}>
{error && <div className="error">{error}</div>}
<label>
Name
<input
type="text"
name="name"
value={formData.name}
onChange={handleChange}
required
/>
</label>
<label>
Email
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
required
/>
</label>
<label>
Message
<textarea
name="message"
value={formData.message}
onChange={handleChange}
required
/>
</label>
<button type="submit" disabled={status === 'loading'}>
{status === 'loading' ? 'Sending...' : 'Send Message'}
</button>
</form>
);
}With File Uploads
javascript
import { useState, useRef } from 'react';
function FileUploadForm() {
const [files, setFiles] = useState([]);
const [status, setStatus] = useState('idle');
const [progress, setProgress] = useState(0);
const fileInputRef = useRef(null);
const handleFileChange = (e) => {
setFiles(Array.from(e.target.files));
};
const handleSubmit = async (e) => {
e.preventDefault();
setStatus('loading');
const formData = new FormData(e.target);
// Use XMLHttpRequest for progress tracking
const xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress', (e) => {
if (e.lengthComputable) {
setProgress(Math.round((e.loaded / e.total) * 100));
}
});
xhr.addEventListener('load', () => {
if (xhr.status >= 200 && xhr.status < 300) {
setStatus('success');
setFiles([]);
fileInputRef.current.value = '';
} else {
setStatus('error');
}
});
xhr.addEventListener('error', () => {
setStatus('error');
});
xhr.open('POST', 'https://atlasforms.app/f/YOUR_FORM_ID');
xhr.send(formData);
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
name="name"
placeholder="Your name"
required
/>
<input
type="file"
name="documents"
ref={fileInputRef}
onChange={handleFileChange}
multiple
accept=".pdf,.doc,.docx"
/>
{files.length > 0 && (
<ul>
{files.map((file, i) => (
<li key={i}>{file.name} ({Math.round(file.size / 1024)} KB)</li>
))}
</ul>
)}
{status === 'loading' && (
<div className="progress-bar">
<div style={{ width: `${progress}%` }}>{progress}%</div>
</div>
)}
<button type="submit" disabled={status === 'loading'}>
{status === 'loading' ? `Uploading ${progress}%` : 'Upload'}
</button>
</form>
);
}TypeScript Version
javascript
import { useState, FormEvent, ChangeEvent } from 'react';
interface FormData {
name: string;
email: string;
message: string;
subscribe: boolean;
}
interface SubmissionResult {
success: true;
submission_id: string;
}
type FormStatus = 'idle' | 'loading' | 'success' | 'error';
function useAtlasForm<T>(formId: string) {
const [status, setStatus] = useState<FormStatus>('idle');
const [error, setError] = useState<string | null>(null);
const [result, setResult] = useState<SubmissionResult | null>(null);
const submit = async (data: T): Promise<SubmissionResult> => {
setStatus('loading');
setError(null);
const response = await fetch(`https://atlasforms.app/f/${formId}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
const json = await response.json();
if (!response.ok) {
setError(json.error || 'Submission failed');
setStatus('error');
throw new Error(json.error);
}
setResult(json);
setStatus('success');
return json;
};
return { submit, status, error, result, isLoading: status === 'loading' };
}
function ContactForm() {
const [formData, setFormData] = useState<FormData>({
name: '',
email: '',
message: '',
subscribe: false,
});
const { submit, status, error, isLoading } = useAtlasForm<FormData>('YOUR_FORM_ID');
const handleChange = (
e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) => {
const { name, value, type } = e.target;
setFormData((prev) => ({
...prev,
[name]: type === 'checkbox' ? (e.target as HTMLInputElement).checked : value,
}));
};
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
try {
await submit(formData);
} catch {
// Error handled by hook
}
};
// ... render form
}With React Hook Form
javascript
import { useForm } from 'react-hook-form';
function ContactForm() {
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
reset,
} = useForm();
const onSubmit = async (data) => {
const response = await fetch('https://atlasforms.app/f/YOUR_FORM_ID', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
if (response.ok) {
reset();
alert('Submitted!');
} else {
const result = await response.json();
alert(result.error || 'Failed');
}
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input
{...register('name', { required: 'Name is required' })}
placeholder="Name"
/>
{errors.name && <span>{errors.name.message}</span>}
<input
{...register('email', {
required: 'Email is required',
pattern: {
value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
message: 'Invalid email',
},
})}
placeholder="Email"
/>
{errors.email && <span>{errors.email.message}</span>}
<textarea
{...register('message', { required: 'Message is required' })}
placeholder="Message"
/>
{errors.message && <span>{errors.message.message}</span>}
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Sending...' : 'Send'}
</button>
</form>
);
}Next.js Server Action
javascript
// app/actions.ts
'use server';
export async function submitContact(formData: FormData) {
const data = {
name: formData.get('name'),
email: formData.get('email'),
message: formData.get('message'),
};
const response = await fetch('https://atlasforms.app/f/YOUR_FORM_ID', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
if (!response.ok) {
const result = await response.json();
return { error: result.error };
}
return { success: true };
}
// app/contact/page.tsx
import { submitContact } from '../actions';
export default function ContactPage() {
return (
<form action={submitContact}>
<input name="name" required />
<input name="email" type="email" required />
<textarea name="message" required />
<button type="submit">Send</button>
</form>
);
}Error Boundary
javascript
import { Component } from 'react';
class FormErrorBoundary extends Component {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true };
}
render() {
if (this.state.hasError) {
return (
<div className="error">
<h2>Something went wrong</h2>
<button onClick={() => this.setState({ hasError: false })}>
Try again
</button>
</div>
);
}
return this.props.children;
}
}
// Usage
<FormErrorBoundary>
<ContactForm />
</FormErrorBoundary>