React Integration
Add a MakeEmWait waitlist to your React application.
There are two ways to add a MakeEmWait waitlist to your React app: use the pre-built embed widget for a quick setup, or call the API directly for full control over the UI.
Using the Embed Widget in React
The embed widget is the fastest way to get a waitlist into your React app. It loads a pre-built form inside a Shadow DOM, so your existing styles won’t interfere with it. Load the embed script in a React component using useEffect:
import { useEffect, useRef } from 'react';
function WaitlistWidget({ waitlistId, theme = 'dark', compact = false }) {
const containerRef = useRef(null);
useEffect(() => {
const script = document.createElement('script');
script.src = 'https://makeemwait.com/assets/js/embed.js';
script.setAttribute('data-waitlist-id', waitlistId);
script.setAttribute('data-theme', theme);
if (compact) script.setAttribute('data-compact', 'true');
containerRef.current.appendChild(script);
return () => {
// Clean up widget on unmount
if (containerRef.current) {
containerRef.current.innerHTML = '';
}
};
}, [waitlistId, theme, compact]);
return <div ref={containerRef} />;
}
export default WaitlistWidget;
Then use it anywhere in your app:
<WaitlistWidget waitlistId="abc123" theme="light" />
The widget renders inside a Shadow DOM, so your app’s styles won’t conflict with it. All embed features work as normal — themes, compact mode, referral tracking, UTM passthrough, returning visitor detection, and social sharing. See the Embed Widget docs for the full list of data- attributes.
Using the API Directly
If you want full control over the look and feel, skip the embed widget and call the MakeEmWait API directly with fetch. This lets you build a completely custom signup form that matches your app’s design system.
import { useState } from 'react';
const API_URL = 'https://makeemwait.com/api/v1';
function WaitlistForm({ waitlistId }) {
const [email, setEmail] = useState('');
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [status, setStatus] = useState('idle'); // idle | loading | success | error
const [result, setResult] = useState(null);
const [error, setError] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
setStatus('loading');
setError('');
try {
// Read referral token from URL if present
const params = new URLSearchParams(window.location.search);
const referredByToken = params.get('ref');
const response = await fetch(
`${API_URL}/public/waitlists/${waitlistId}/signups`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email,
first_name: firstName,
last_name: lastName,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
...(referredByToken && { referred_by_token: referredByToken }),
}),
}
);
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || 'Something went wrong');
}
setResult(data);
setStatus('success');
} catch (err) {
setError(err.message);
setStatus('error');
}
};
if (status === 'success' && result) {
return (
<div>
<h3>You're on the list!</h3>
<p>You're #{result.position} in line.</p>
{result.referral_token && (
<p>
Share your referral link to move up:{' '}
<code>
{window.location.origin + window.location.pathname}?ref=
{result.referral_token}
</code>
</p>
)}
</div>
);
}
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="you@example.com"
required
/>
<input
type="text"
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
placeholder="First name"
/>
<input
type="text"
value={lastName}
onChange={(e) => setLastName(e.target.value)}
placeholder="Last name"
/>
<button type="submit" disabled={status === 'loading'}>
{status === 'loading' ? 'Joining...' : 'Join the Waitlist'}
</button>
{error && <p style=>{error}</p>}
</form>
);
}
export default WaitlistForm;
The public signup endpoint does not require authentication, so you can call it directly from the browser. The timezone field is optional but recommended — it lets you segment signups by time zone later.
TypeScript Types
If your project uses TypeScript, add these interfaces to get type safety for the signup API responses. They cover the success response, error shape, and waitlist configuration object:
interface SignupResponse {
email: string;
first_name: string;
last_name: string;
position: number;
referral_token: string;
referral_count: number;
created_at: string;
total_signups: number;
redirect_url?: string;
}
interface SignupError {
error: string;
error_code: string;
request_id: string;
}
interface WaitlistConfig {
name: string;
headline: string;
subheadline: string;
hero_image_url: string | null;
name_field: 'optional' | 'required' | 'hidden';
phone_field: 'optional' | 'required' | 'hidden';
signup_count: number;
custom_questions: CustomQuestion[];
show_social_proof: boolean;
require_consent: boolean;
consent_text: string | null;
status: 'open' | 'closed';
referral_boost: number;
hide_branding: boolean;
hide_position: boolean;
position_offset: number;
require_email_verification: boolean;
}
interface CustomQuestion {
id: string;
label: string;
type: 'text' | 'dropdown' | 'checkbox';
required: boolean;
options?: string[];
}
SignupResponse is what the POST /public/waitlists/{id}/signups endpoint returns on success. SignupError is the shape of all error responses. WaitlistConfig is what GET /public/waitlists/{id}/config returns — use it to dynamically render form fields based on the waitlist’s settings.
Custom Hook
For a cleaner separation of concerns, extract the signup logic into a reusable hook. This is useful when you have multiple components that need to trigger signups, or when you want to keep your form components focused on rendering:
import { useState, useCallback } from 'react';
const API_URL = 'https://makeemwait.com/api/v1';
interface SignupFields {
email: string;
first_name?: string;
last_name?: string;
phone?: string;
referred_by_token?: string;
}
export function useWaitlist(waitlistId: string) {
const [status, setStatus] = useState<
'idle' | 'loading' | 'success' | 'error'
>('idle');
const [data, setData] = useState<SignupResponse | null>(null);
const [error, setError] = useState<string>('');
const signup = useCallback(
async (fields: SignupFields) => {
setStatus('loading');
setError('');
try {
const response = await fetch(
`${API_URL}/public/waitlists/${waitlistId}/signups`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
...fields,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
}),
}
);
const result = await response.json();
if (!response.ok) throw new Error(result.error);
setData(result);
setStatus('success');
return result;
} catch (err) {
setError(
err instanceof Error ? err.message : 'Something went wrong'
);
setStatus('error');
return null;
}
},
[waitlistId]
);
return { signup, status, data, error };
}
Then use it in any component:
import { useWaitlist } from './useWaitlist';
function SignupForm() {
const { signup, status, data, error } = useWaitlist('abc123');
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const form = new FormData(e.currentTarget);
await signup({
email: form.get('email') as string,
first_name: form.get('first_name') as string,
});
};
if (status === 'success' && data) {
return <p>You're #{data.position} in line!</p>;
}
return (
<form onSubmit={handleSubmit}>
<input name="email" type="email" placeholder="you@example.com" required />
<input name="first_name" type="text" placeholder="First name" />
<button type="submit" disabled={status === 'loading'}>
{status === 'loading' ? 'Joining...' : 'Join the Waitlist'}
</button>
{error && <p style=>{error}</p>}
</form>
);
}
The hook manages loading state, error handling, and the API call. Your component only deals with rendering.
Related
These guides cover other frameworks and the full API surface:
- HTML / Vanilla JS Integration — embed on static sites
- Next.js Integration — server-side rendering and API routes
- Embed Widget — full embed configuration reference
- API Reference — all public and authenticated endpoints