Skip to main content

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

projectId
string
required
Your Vouch project ID
apiKey
string
required
Your client API key
options
object
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

validate
function
Function to validate an email. Returns the validation response.
validate(email: string, options?: ValidationRequestOptions): Promise<ValidationResponse>
loading
boolean
Whether validation is in progress
data
ValidationResponse | null
Validation response (null if not yet validated). Contains checks, metadata, recommendation, and signals.
error
Error | null
Error if validation failed
reset
function
Reset state to initial values
reset(): void

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

Full Signup Form

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
  }
};
Avoid validating on every keystroke:
useEffect(() => {
  const timeout = setTimeout(() => {
    if (email) validate(email);
  }, 500);

  return () => clearTimeout(timeout);
}, [email]);

Next Steps