A
Atlas
...

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:

  • Keep your form in Sandbox Mode (default)
  • Go to Form Settings → Localhost Testing
  • Click Register My IP
  • 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>