React Integration
SPA-specific patterns for React, Next.js, and other frameworks
React Integration
This guide covers SPA-specific patterns for integrating BotSigged with React, Next.js, and similar frameworks. Single-page applications have unique considerations around component lifecycles, client-side routing, and form handling.
Installation
Install the SDK via npm or your preferred package manager:
npm install @botsigged/sdk
# or
yarn add @botsigged/sdk
# or
pnpm add @botsigged/sdk
Basic Setup
Provider Pattern
Create a context provider to manage the SDK instance across your application:
// lib/botsigged.tsx
import { createContext, useContext, useEffect, useState, ReactNode } from 'react';
import { BotSigged, ScoreUpdate } from '@botsigged/sdk';
interface BotSiggedContextValue {
sdk: BotSigged | null;
score: ScoreUpdate | null;
isConnected: boolean;
isReady: boolean;
}
const BotSiggedContext = createContext<BotSiggedContextValue>({
sdk: null,
score: null,
isConnected: false,
isReady: false,
});
export function BotSiggedProvider({
children,
apiKey
}: {
children: ReactNode;
apiKey: string;
}) {
const [sdk, setSdk] = useState<BotSigged | null>(null);
const [score, setScore] = useState<ScoreUpdate | null>(null);
const [isConnected, setIsConnected] = useState(false);
const [isReady, setIsReady] = useState(false);
useEffect(() => {
const instance = new BotSigged({
apiKey,
autoStart: true,
onScoreUpdate: (newScore) => {
setScore(newScore);
setIsReady(true);
},
onConnectionChange: setIsConnected,
});
setSdk(instance);
return () => {
instance.stop();
};
}, [apiKey]);
return (
<BotSiggedContext.Provider value={{ sdk, score, isConnected, isReady }}>
{children}
</BotSiggedContext.Provider>
);
}
export function useBotSigged() {
return useContext(BotSiggedContext);
}
Using the Provider
Wrap your application with the provider:
// app/layout.tsx (Next.js App Router)
import { BotSiggedProvider } from '@/lib/botsigged';
export default function RootLayout({ children }) {
return (
<html>
<body>
<BotSiggedProvider apiKey={process.env.NEXT_PUBLIC_BOTSIGGED_KEY!}>
{children}
</BotSiggedProvider>
</body>
</html>
);
}
// pages/_app.tsx (Next.js Pages Router)
import { BotSiggedProvider } from '@/lib/botsigged';
export default function App({ Component, pageProps }) {
return (
<BotSiggedProvider apiKey={process.env.NEXT_PUBLIC_BOTSIGGED_KEY!}>
<Component {...pageProps} />
</BotSiggedProvider>
);
}
Form Protection
Basic Protected Form
import { useBotSigged } from '@/lib/botsigged';
import { useState } from 'react';
export function SignupForm() {
const { sdk, score, isReady } = useBotSigged();
const [isSubmitting, setIsSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setError(null);
setIsSubmitting(true);
try {
// Wait for bot detection before submitting
const { score: finalScore, timedOut } = await sdk?.waitUntilReady() ?? {};
if (finalScore && finalScore > 70) {
setError('Unable to verify your request. Please try again.');
return;
}
const formData = new FormData(e.currentTarget);
await fetch('/api/signup', {
method: 'POST',
body: formData,
});
} finally {
setIsSubmitting(false);
}
};
return (
<form onSubmit={handleSubmit}>
<input name="email" type="email" required />
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Submitting...' : 'Sign Up'}
</button>
{error && <p className="error">{error}</p>}
</form>
);
}
Using withProtection
For cleaner code, use the withProtection wrapper:
import { useBotSigged } from '@/lib/botsigged';
export function ContactForm() {
const { sdk } = useBotSigged();
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
// Wrap your submission logic
const protectedSubmit = sdk?.withProtection(async () => {
const response = await fetch('/api/contact', {
method: 'POST',
body: JSON.stringify(Object.fromEntries(formData)),
headers: { 'Content-Type': 'application/json' },
});
return response.json();
});
await protectedSubmit?.();
};
return (
<form onSubmit={handleSubmit}>
{/* form fields */}
</form>
);
}
Conditional Submit Button
Show users when verification is in progress:
import { useBotSigged } from '@/lib/botsigged';
export function SubmitButton() {
const { sdk, isReady, score } = useBotSigged();
const canSubmit = sdk?.canSubmit();
const isBlocked = score && score.bot_score > 70;
return (
<button
type="submit"
disabled={!canSubmit?.allowed || isBlocked}
>
{!isReady ? 'Verifying...' : isBlocked ? 'Blocked' : 'Submit'}
</button>
);
}
Custom Hook
Create a custom hook for common patterns:
// hooks/useBotProtection.ts
import { useBotSigged } from '@/lib/botsigged';
import { useState, useCallback } from 'react';
interface UseProtectedSubmitOptions {
threshold?: number;
onBlocked?: (score: number) => void;
}
export function useProtectedSubmit<T>(
submitFn: () => Promise<T>,
options: UseProtectedSubmitOptions = {}
) {
const { sdk } = useBotSigged();
const { threshold = 70, onBlocked } = options;
const [isSubmitting, setIsSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
const submit = useCallback(async () => {
setIsSubmitting(true);
setError(null);
try {
const { score } = await sdk?.waitUntilReady() ?? {};
if (score && score > threshold) {
onBlocked?.(score);
setError('Request blocked due to suspicious activity');
return null;
}
return await submitFn();
} catch (err) {
setError(err instanceof Error ? err.message : 'Submission failed');
return null;
} finally {
setIsSubmitting(false);
}
}, [sdk, submitFn, threshold, onBlocked]);
return { submit, isSubmitting, error };
}
Usage:
function MyForm() {
const { submit, isSubmitting, error } = useProtectedSubmit(
async () => {
const res = await fetch('/api/submit', { method: 'POST' });
return res.json();
},
{ threshold: 60 }
);
return (
<form onSubmit={(e) => { e.preventDefault(); submit(); }}>
<button disabled={isSubmitting}>Submit</button>
{error && <p>{error}</p>}
</form>
);
}
Score Display Component
For debugging or transparency, display the current score:
import { useBotSigged } from '@/lib/botsigged';
export function BotScoreIndicator() {
const { score, isConnected, isReady } = useBotSigged();
if (!isConnected) {
return <span className="status disconnected">Disconnected</span>;
}
if (!isReady) {
return <span className="status loading">Analyzing...</span>;
}
const classification = score?.classification ?? 'unknown';
const botScore = score?.bot_score ?? 0;
return (
<div className={`score-indicator ${classification}`}>
<span>Score: {botScore}</span>
<span>{classification}</span>
</div>
);
}
Client-Side Routing
Handling Route Changes
The SDK automatically tracks page visibility, but for SPAs with client-side routing, you may want to track virtual page views:
// Next.js App Router
'use client';
import { usePathname } from 'next/navigation';
import { useEffect } from 'react';
import { useBotSigged } from '@/lib/botsigged';
export function RouteTracker() {
const pathname = usePathname();
const { sdk } = useBotSigged();
useEffect(() => {
// SDK continues tracking across route changes
// No action needed - behavioral signals persist
}, [pathname, sdk]);
return null;
}
Resetting on Logout
When a user logs out, you may want to end the current session:
async function handleLogout() {
const { sdk } = useBotSigged();
// Stop current session
await sdk?.stop();
// Clear auth state
await signOut();
// Optionally restart for the login page
await sdk?.start();
}
Next.js Specific
App Router (Server Components)
The SDK must run on the client. Use the 'use client' directive:
// components/BotSiggedProvider.tsx
'use client';
import { BotSiggedProvider as Provider } from '@/lib/botsigged';
export function BotSiggedProvider({ children }: { children: React.ReactNode }) {
return (
<Provider apiKey={process.env.NEXT_PUBLIC_BOTSIGGED_KEY!}>
{children}
</Provider>
);
}
Dynamic Import (Reduce Initial Bundle)
Load the SDK only when needed:
import dynamic from 'next/dynamic';
const BotSiggedProvider = dynamic(
() => import('@/components/BotSiggedProvider').then(mod => mod.BotSiggedProvider),
{ ssr: false }
);
export default function Layout({ children }) {
return <BotSiggedProvider>{children}</BotSiggedProvider>;
}
Middleware Integration
For server-side decisions, pass the session ID to your API routes:
const { sdk } = useBotSigged();
await fetch('/api/submit', {
method: 'POST',
headers: {
'X-BotSigged-Session': sdk?.getSessionId() ?? '',
},
body: JSON.stringify(data),
});
Then verify server-side:
// app/api/submit/route.ts
export async function POST(request: Request) {
const sessionId = request.headers.get('X-BotSigged-Session');
// Query BotSigged API for session score
const session = await fetch(`${BOTSIGGED_API}/sessions/${sessionId}`);
const { bot_score } = await session.json();
if (bot_score > 70) {
return Response.json({ error: 'Blocked' }, { status: 403 });
}
// Process legitimate request
}
React Hook Form Integration
import { useForm } from 'react-hook-form';
import { useBotSigged } from '@/lib/botsigged';
interface FormData {
email: string;
message: string;
}
export function ContactForm() {
const { sdk } = useBotSigged();
const { register, handleSubmit, formState } = useForm<FormData>();
const onSubmit = async (data: FormData) => {
const { score } = await sdk?.waitUntilReady() ?? {};
if (score && score > 70) {
throw new Error('Submission blocked');
}
await fetch('/api/contact', {
method: 'POST',
body: JSON.stringify({
...data,
sessionId: sdk?.getSessionId(),
}),
});
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('email', { required: true })} />
<textarea {...register('message', { required: true })} />
<button disabled={formState.isSubmitting}>Send</button>
</form>
);
}
Troubleshooting
SDK Not Initializing
Ensure the SDK is only initialized on the client:
useEffect(() => {
// Only runs on client
const instance = new BotSigged({ apiKey });
// ...
}, []);
Score Always Null
The first score arrives after the WebSocket connects and initial analysis completes. Use isReady to track this:
const { score, isReady } = useBotSigged();
if (!isReady) {
return <Loading />;
}
Multiple Instances
The SDK defaults to singleton mode with BotSigged.init(). In React with strict mode, use new BotSigged() with proper cleanup:
useEffect(() => {
const instance = new BotSigged({ apiKey, autoStart: true });
return () => {
instance.stop(); // Clean up on unmount
};
}, []);
Form Events Not Tracked
Ensure form elements are in the DOM when the SDK starts. For dynamically rendered forms, the SDK automatically observes new elements.
For forms in portals or iframes, you may need manual tracking (contact support for advanced use cases).