Overview
The Vouch React SDK provides hooks and components for seamless email validation in React applications with automatic device fingerprinting.
Package: @vouch-in/react
React Version: 16.8+ (hooks required)
TypeScript: Full type definitions included
Installation
npm install @vouch-in/react
Quick Start
import { VouchProvider, useValidateEmail } from '@vouch-in/react';
function App() {
return (
<VouchProvider
projectId="your-project-id"
apiKey="your-client-api-key"
>
<SignupForm />
</VouchProvider>
);
}
function SignupForm() {
const { validate, loading, data, error } = useValidateEmail();
const [email, setEmail] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
const result = await validate(email);
if (result.recommendation === 'allow') {
// Proceed with signup
console.log('Email is valid!');
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
<button disabled={loading}>
{loading ? 'Validating...' : 'Sign Up'}
</button>
{data && !data.checks.disposable?.pass && (
<p className="error">Disposable emails are not allowed</p>
)}
{error && (
<p className="error">{error.message}</p>
)}
</form>
);
}
VouchProvider
Wrap your app with VouchProvider to enable Vouch throughout your component tree:
import { VouchProvider } from '@vouch-in/react';
function App() {
return (
<VouchProvider
projectId={process.env.REACT_APP_VOUCH_PROJECT_ID}
apiKey={process.env.REACT_APP_VOUCH_API_KEY}
options={{
endpoint: 'https://api.vouch.expert',
apiVersion: 'latest', // or 1, 2, etc.
}}
>
<YourApp />
</VouchProvider>
);
}
Props
Optional configuration:
endpoint - API endpoint URL (default: “https://api.vouch.expert”)
apiVersion - API version number or “latest” (default: “latest”)
useValidateEmail()
Primary hook for email validation:
import { useValidateEmail } from '@vouch-in/react';
function SignupForm() {
const {
validate,
loading,
data,
error,
reset
} = useValidateEmail();
const handleValidate = async () => {
const result = await validate('[email protected]');
if (result.recommendation === 'allow') {
// All checks passed, proceed with signup
proceedWithSignup();
} else {
// Check individual validation results
if (!result.checks.disposable?.pass) {
showError('Disposable emails are not allowed');
}
}
};
return (
<div>
<button onClick={handleValidate} disabled={loading}>
Validate
</button>
{loading && <Spinner />}
{error && <Error message={error.message} />}
{data && <ValidationResult data={data} />}
</div>
);
}
Return Values
Function to validate an email. Returns the validation response.validate(email: string, options?: ValidationRequestOptions): Promise<ValidationResponse>
Whether validation is in progress
data
ValidationResponse | null
Validation response (null if not yet validated). Contains checks, metadata, recommendation, and signals.
Error if validation failed
Reset state to initial values
ValidationResponse Type
interface ValidationResponse {
checks: Record<string, CheckResult>;
metadata: {
fingerprintHash: string | null;
previousSignups: number;
totalLatency: number;
};
recommendation: 'allow' | 'block' | 'flag';
signals: string[];
}
interface CheckResult {
pass: boolean;
error?: string;
latency: number;
metadata?: Record<string, unknown>;
}
useVouch()
Access the Vouch instance directly:
import { useVouch } from '@vouch-in/react';
function Component() {
const vouch = useVouch();
useEffect(() => {
async function getFingerprint() {
const fp = await vouch.generateFingerprint();
console.log('Canvas hash:', fp.canvas.hash);
}
getFingerprint();
}, [vouch]);
}
useFingerprint()
Hook to generate and access device fingerprint:
import { useFingerprint } from '@vouch-in/react';
function DeviceInfo() {
const { fingerprint, loading, error, generate } = useFingerprint();
return (
<div>
<button onClick={generate} disabled={loading}>
Generate Fingerprint
</button>
{fingerprint && (
<div>
<p>Canvas: {fingerprint.canvas.hash}</p>
<p>WebGL: {fingerprint.webgl.hash}</p>
<p>Screen: {fingerprint.hardware.screen.width}x{fingerprint.hardware.screen.height}</p>
</div>
)}
</div>
);
}
Complete Examples
import { useState } from 'react';
import { VouchProvider, useValidateEmail } from '@vouch-in/react';
function App() {
return (
<VouchProvider projectId={PROJECT_ID} apiKey={API_KEY}>
<SignupForm />
</VouchProvider>
);
}
function SignupForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const { validate, loading, data } = useValidateEmail();
const handleSubmit = async (e) => {
e.preventDefault();
// Validate email first
const result = await validate(email);
const { checks, metadata, recommendation } = result;
// Check critical validations
if (!checks.syntax?.pass) {
alert('Invalid email format');
return;
}
if (!checks.disposable?.pass) {
alert('Disposable emails are not allowed');
return;
}
// Check suspicious patterns
if (metadata.previousSignups > 5) {
const proceed = confirm(
'This device has been used with multiple emails. Continue anyway?'
);
if (!proceed) return;
}
// Proceed with signup
await createAccount({ email, password });
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>Email</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
{data && !data.checks.disposable?.pass && (
<p className="warning">Disposable email detected</p>
)}
</div>
<div>
<label>Password</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
<button type="submit" disabled={loading}>
{loading ? 'Validating...' : 'Sign Up'}
</button>
</form>
);
}
Real-time Validation
import { useState, useEffect } from 'react';
import { useValidateEmail } from '@vouch-in/react';
function EmailInput() {
const [email, setEmail] = useState('');
const { validate, loading, data } = useValidateEmail();
// Debounced validation
useEffect(() => {
if (!email.includes('@')) return;
const timeout = setTimeout(() => {
validate(email);
}, 500);
return () => clearTimeout(timeout);
}, [email, validate]);
return (
<div className="email-input">
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Enter your email"
/>
{loading && <Spinner />}
{data && (
<ValidationIndicator data={data} />
)}
</div>
);
}
function ValidationIndicator({ data }) {
if (!data) return null;
const { checks, recommendation } = data;
if (recommendation === 'allow') {
return <span className="valid">Valid email</span>;
}
if (!checks.syntax?.pass) {
return <span className="error">Invalid email format</span>;
}
if (!checks.disposable?.pass) {
return <span className="error">Disposable emails not allowed</span>;
}
return <span className="warning">Email flagged for review</span>;
}
TypeScript
import { useState } from 'react';
import { useValidateEmail, ValidationResponse } from '@vouch-in/react';
function SignupForm() {
const [email, setEmail] = useState<string>('');
const {
validate,
loading,
data,
error
} = useValidateEmail();
const handleValidate = async (): Promise<void> => {
const result: ValidationResponse = await validate(email);
if (result.recommendation !== 'allow') {
console.log('Email flagged:', result.recommendation);
return;
}
if (!result.checks.disposable?.pass) {
console.log('Disposable email detected');
}
// Proceed with valid email
await submitForm(email);
};
return (
<form onSubmit={(e) => { e.preventDefault(); handleValidate(); }}>
<input
type="email"
value={email}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setEmail(e.target.value)
}
/>
<button disabled={loading}>Validate</button>
</form>
);
}
Next.js Integration
// app/layout.tsx (App Router)
import { VouchProvider } from '@vouch-in/react';
export default function RootLayout({ children }) {
return (
<html>
<body>
<VouchProvider
projectId={process.env.NEXT_PUBLIC_VOUCH_PROJECT_ID!}
apiKey={process.env.NEXT_PUBLIC_VOUCH_CLIENT_KEY!}
>
{children}
</VouchProvider>
</body>
</html>
);
}
// app/signup/page.tsx
'use client';
import { useValidateEmail } from '@vouch-in/react';
export default function SignupPage() {
const { validate, loading, data } = useValidateEmail();
// ... rest of component
}
Best Practices
Place VouchProvider at your app’s root for consistent access:// Good
<VouchProvider>
<App />
</VouchProvider>
// Bad - multiple providers
<Component>
<VouchProvider>...</VouchProvider>
</Component>
Always show loading feedback during validation:<button disabled={loading}>
{loading ? 'Validating...' : 'Submit'}
</button>
Clear validation state after successful submission:const { validate, reset } = useValidateEmail();
const handleSubmit = async (email) => {
const result = await validate(email);
if (result.recommendation === 'allow') {
await createAccount(email);
reset(); // Clear state
}
};
Debounce Real-time Validation
Avoid validating on every keystroke:useEffect(() => {
const timeout = setTimeout(() => {
if (email) validate(email);
}, 500);
return () => clearTimeout(timeout);
}, [email]);
Next Steps