React Integration

This guide covers integrating TraceScout into React applications, including best practices for initialization and common patterns.

Basic Setup

Create a TraceScout Provider

Create a component to handle SDK initialization:

// components/TraceScoutProvider.tsx
'use client';

import { useEffect, useRef } from 'react';

interface TraceScoutProviderProps {
  children: React.ReactNode;
}

export function TraceScoutProvider({ children }: TraceScoutProviderProps) {
  const initialized = useRef(false);

  useEffect(() => {
    // Prevent double initialization in React Strict Mode
    if (initialized.current) return;
    initialized.current = true;

    // Load SDK from CDN
    const script = document.createElement('script');
    script.src = 'https://cdn.tracescout.com/sdk/tracescout.min.js';
    script.async = true;
    
    script.onload = () => {
      window.TraceScout?.init({
        apiKey: process.env.NEXT_PUBLIC_TRACESCOUT_API_KEY!,
        organizationId: process.env.NEXT_PUBLIC_TRACESCOUT_ORGANIZATION_ID!,
        projectId: process.env.NEXT_PUBLIC_TRACESCOUT_PROJECT_ID!,
        environment: process.env.NODE_ENV,
        enableSessionRecording: true,
        recordingMaskAllInputs: true,
      });
    };

    document.head.appendChild(script);

    return () => {
      // Cleanup on unmount
      window.TraceScout?.destroy?.();
    };
  }, []);

  return <>{children}</>;
}

Add to App

Wrap your application with the provider:

// app/layout.tsx or pages/_app.tsx
import { TraceScoutProvider } from '@/components/TraceScoutProvider';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <TraceScoutProvider>
          {children}
        </TraceScoutProvider>
      </body>
    </html>
  );
}

Using npm Package

If you prefer the npm package:

// components/TraceScoutProvider.tsx
'use client';

import { useEffect, useRef } from 'react';
import TraceScout from '@tracescout/suite';

export function TraceScoutProvider({ children }: { children: React.ReactNode }) {
  const initialized = useRef(false);

  useEffect(() => {
    if (initialized.current) return;
    initialized.current = true;

    TraceScout.init({
      apiKey: process.env.NEXT_PUBLIC_TRACESCOUT_API_KEY!,
      organizationId: process.env.NEXT_PUBLIC_TRACESCOUT_ORGANIZATION_ID!,
      projectId: process.env.NEXT_PUBLIC_TRACESCOUT_PROJECT_ID!,
      environment: process.env.NODE_ENV,
    });

    return () => {
      TraceScout.destroy?.();
    };
  }, []);

  return <>{children}</>;
}

Client Component Required

TraceScout must run in the browser. Make sure to use 'use client' directive in Next.js 13+ App Router.

Custom Hooks

useTraceScout Hook

Create a hook for easy access to TraceScout methods:

// hooks/useTraceScout.ts
import { useCallback } from 'react';

export function useTraceScout() {
  const trackEvent = useCallback((name: string, data?: Record<string, any>) => {
    window.TraceScout?.addCustomEvent?.(name, data);
  }, []);

  const identifyUser = useCallback((userId: string, traits?: Record<string, any>) => {
    window.TraceScout?.setUser?.(userId, traits);
  }, []);

  const startRecording = useCallback(() => {
    return window.TraceScout?.startSessionRecording?.();
  }, []);

  const stopRecording = useCallback(() => {
    return window.TraceScout?.stopSessionRecording?.();
  }, []);

  return {
    trackEvent,
    identifyUser,
    startRecording,
    stopRecording,
  };
}

Usage in Components

import { useTraceScout } from '@/hooks/useTraceScout';

function CheckoutButton() {
  const { trackEvent } = useTraceScout();

  const handleClick = () => {
    trackEvent('checkout_started', {
      cartValue: 99.99,
      itemCount: 3,
    });
    // Continue with checkout...
  };

  return <button onClick={handleClick}>Checkout</button>;
}

User Identification

Identify users when they log in:

function LoginHandler() {
  const { identifyUser } = useTraceScout();

  const handleLogin = async (credentials: Credentials) => {
    const user = await loginUser(credentials);
    
    // Identify user in TraceScout
    identifyUser(user.id, {
      email: user.email,
      name: user.name,
      plan: user.subscription,
    });
  };

  return <LoginForm onSubmit={handleLogin} />;
}

Error Boundaries

Integrate with React Error Boundaries:

import { Component, ErrorInfo, ReactNode } from 'react';

interface Props {
  children: ReactNode;
  fallback: ReactNode;
}

interface State {
  hasError: boolean;
}

class ErrorBoundary extends Component<Props, State> {
  state = { hasError: false };

  static getDerivedStateFromError(): State {
    return { hasError: true };
  }

  componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    // Track error in TraceScout
    window.TraceScout?.addCustomEvent?.('react_error', {
      error: error.message,
      stack: error.stack,
      componentStack: errorInfo.componentStack,
    });
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback;
    }
    return this.props.children;
  }
}

export default ErrorBoundary;

Privacy in Forms

Use CSS classes to control recording:

function SensitiveForm() {
  return (
    <form>
      {/* This input will be masked */}
      <input
        type="text"
        name="email"
        className="tracescout-mask"
        placeholder="Email"
      />
      
      {/* This entire section won't be recorded */}
      <div className="tracescout-block">
        <input type="password" name="password" />
        <input type="text" name="ssn" placeholder="SSN" />
      </div>
      
      <button type="submit">Submit</button>
    </form>
  );
}

TypeScript Support

Add type definitions:

// types/tracescout.d.ts
declare global {
  interface Window {
    TraceScout?: {
      init: (config: TraceScoutConfig) => Promise<void>;
      startSessionRecording: (options?: RecordingOptions) => SessionInfo;
      stopSessionRecording: () => SessionResult;
      addCustomEvent: (name: string, data?: Record<string, any>) => void;
      setUser: (userId: string, traits?: Record<string, any>) => void;
      destroy?: () => void;
    };
  }
}

interface TraceScoutConfig {
  apiKey: string;
  organizationId: string;
  projectId: string;
  environment: string;
  enableSessionRecording?: boolean;
  recordingMaskAllInputs?: boolean;
  // ... other options
}

interface SessionInfo {
  sessionId: string;
  startTime: number;
  status: string;
}

interface SessionResult extends SessionInfo {
  duration: number;
  totalEvents: number;
  events: any[];
}

export {};

Best Practices

Performance Tips

  1. Initialize TraceScout as early as possible
  2. Use the useRef pattern to prevent double initialization
  3. Lazy load the SDK in non-critical paths
  4. Configure appropriate chunk sizes for your use case

Conditional Recording

Only record in production:

useEffect(() => {
  if (process.env.NODE_ENV !== 'production') return;
  
  // Initialize TraceScout...
}, []);

Recording Specific User Flows

function CheckoutFlow() {
  const { startRecording, stopRecording, trackEvent } = useTraceScout();
  
  useEffect(() => {
    // Start recording when entering checkout
    startRecording();
    trackEvent('checkout_flow_started');
    
    return () => {
      // Stop when leaving
      stopRecording();
      trackEvent('checkout_flow_ended');
    };
  }, []);

  return <CheckoutSteps />;
}

See Also