Hooks Reference#

Complete documentation for all hooks provided by @pulsora/react.

Available Hooks#

usePulsora#

Access the underlying Pulsora instance for advanced use cases.

Syntax#

usePulsora(): PulsoraCore

Returns#

The initialized Pulsora instance from @pulsora/core.

Example#

import { usePulsora } from '@pulsora/react';

function AdvancedTracking() {
  const pulsora = usePulsora();

  const handleCheckout = async () => {
    // Get attribution data for payment
    const fingerprint = await pulsora.getVisitorFingerprint();
    const sessionId = pulsora.getSessionId();

    // Send to payment processor
    await createCheckout({
      visitor_fingerprint: fingerprint,
      session_id: sessionId,
    });
  };

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

Use Cases#

  • Access visitor fingerprint for attribution
  • Get session ID for server correlation
  • Check initialization status
  • Access advanced Pulsora methods

TypeScript#

import { usePulsora } from '@pulsora/react';
import type { PulsoraCore } from '@pulsora/core';

function Component() {
  const pulsora: PulsoraCore = usePulsora();
  // Full type safety
}

usePageview#

Automatically track pageviews with optional triggers.

Syntax#

usePageview(options?: UsePageviewOptions): void

interface UsePageviewOptions {
  trigger?: any;        // Value that triggers pageview when changed
  disabled?: boolean;   // Disable tracking
}

Parameters#

  • options (optional) - Configuration object
    • trigger - Any value that triggers a pageview when it changes
    • disabled - Set to true to disable tracking

Examples#

Basic Usage#

import { usePageview } from '@pulsora/react';

function Page() {
  usePageview(); // Tracks once on mount

  return <div>Page Content</div>;
}

With React Router#

import { useLocation } from 'react-router-dom';
import { usePageview } from '@pulsora/react';

function App() {
  const location = useLocation();

  // Track pageview on route change
  usePageview({ trigger: location.pathname });

  return <Routes>...</Routes>;
}

With Next.js App Router#

'use client';

import { usePathname } from 'next/navigation';
import { usePageview } from '@pulsora/react';

export function PageviewTracker() {
  const pathname = usePathname();

  usePageview({ trigger: pathname });

  return null;
}

Conditional Tracking#

function ConditionalPage({ shouldTrack }) {
  usePageview({
    disabled: !shouldTrack,
  });

  return <div>...</div>;
}

Custom Pageview Data#

import { usePulsora, usePageview } from '@pulsora/react';

function CustomPageview() {
  const pulsora = usePulsora();
  const [category, setCategory] = useState('all');

  // Track category changes as pageviews
  usePageview({ trigger: category });

  // Or manually with custom data
  const trackCustomPageview = () => {
    pulsora.pageview({
      url: `/products?category=${category}`,
      title: `Products - ${category}`,
    });
  };

  return (
    <select onChange={(e) => setCategory(e.target.value)}>
      <option value="all">All Products</option>
      <option value="electronics">Electronics</option>
    </select>
  );
}

Best Practices#

  1. Route-Level Tracking: Place usePageview at the router level for SPAs
  2. Avoid Duplicates: Don't use in every component if using route-level tracking
  3. Meaningful Triggers: Use values that represent actual navigation

useEvent#

Track custom events with a simple function interface.

Syntax#

useEvent(): TrackEventFunction

type TrackEventFunction = (
  eventName: string,
  eventData?: Record<string, any>
) => Promise<void>

Returns#

A function to track events.

Examples#

Basic Event Tracking#

import { useEvent } from '@pulsora/react';

function Button() {
  const trackEvent = useEvent();

  return <button onClick={() => trackEvent('button_click')}>Click Me</button>;
}

Event with Properties#

function ProductCard({ product }) {
  const trackEvent = useEvent();

  const handleAddToCart = () => {
    trackEvent('add_to_cart', {
      product_id: product.id,
      product_name: product.name,
      price: product.price,
      currency: 'USD',
      category: product.category,
    });

    // Add to cart logic...
  };

  return (
    <div>
      <h3>{product.name}</h3>
      <button onClick={handleAddToCart}>Add to Cart</button>
    </div>
  );
}

Form Tracking#

function ContactForm() {
  const trackEvent = useEvent();
  const [formStarted, setFormStarted] = useState(false);

  const handleFocus = () => {
    if (!formStarted) {
      trackEvent('form_start', { form_id: 'contact' });
      setFormStarted(true);
    }
  };

  const handleSubmit = async (e) => {
    e.preventDefault();

    trackEvent('form_submit', {
      form_id: 'contact',
      fields_filled: ['name', 'email', 'message'],
    });

    // Submit form...
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name="email" onFocus={handleFocus} />
      <button type="submit">Submit</button>
    </form>
  );
}

Video Tracking#

function VideoPlayer({ video }) {
  const trackEvent = useEvent();
  const [progress, setProgress] = useState(0);

  const handlePlay = () => {
    trackEvent('video_play', {
      video_id: video.id,
      video_title: video.title,
      duration: video.duration,
    });
  };

  const handleProgress = (currentTime) => {
    const percentage = (currentTime / video.duration) * 100;

    // Track quartiles
    if (percentage >= 25 && progress < 25) {
      trackEvent('video_progress', {
        video_id: video.id,
        milestone: '25%',
      });
      setProgress(25);
    }
    // ... track 50%, 75%, 100%
  };

  return (
    <video
      onPlay={handlePlay}
      onTimeUpdate={(e) => handleProgress(e.target.currentTime)}
    >
      <source src={video.url} />
    </video>
  );
}

Error Tracking#

function ErrorBoundary({ children }) {
  const trackEvent = useEvent();

  const logError = (error, errorInfo) => {
    trackEvent('javascript_error', {
      message: error.toString(),
      stack: error.stack?.substring(0, 500),
      component_stack: errorInfo.componentStack?.substring(0, 500),
    });
  };

  return <ReactErrorBoundary onError={logError}>{children}</ReactErrorBoundary>;
}

TypeScript#

import { useEvent } from '@pulsora/react';

interface CartEvent {
  product_id: string;
  price: number;
  quantity: number;
}

function Cart() {
  const trackEvent = useEvent();

  const addToCart = (product: Product) => {
    const eventData: CartEvent = {
      product_id: product.id,
      price: product.price,
      quantity: 1,
    };

    trackEvent('add_to_cart', eventData);
  };
}

Hook Composition#

Custom Analytics Hook#

function useAnalytics() {
  const pulsora = usePulsora();
  const trackEvent = useEvent();

  const trackPurchase = async (orderData) => {
    const fingerprint = await pulsora.getVisitorFingerprint();
    const sessionId = pulsora.getSessionId();

    await trackEvent('purchase', {
      order_id: orderData.id,
      total: orderData.total,
      items: orderData.items.length,
    });

    // Send attribution data to backend
    return { fingerprint, sessionId };
  };

  return {
    trackEvent,
    trackPurchase,
  };
}

Page Analytics Hook#

function usePageAnalytics(pageName: string) {
  const trackEvent = useEvent();
  const startTime = useRef(Date.now());

  usePageview();

  useEffect(() => {
    // Track page view duration on unmount
    return () => {
      const duration = Date.now() - startTime.current;
      trackEvent('page_view_duration', {
        page: pageName,
        duration_ms: duration,
        duration_seconds: Math.round(duration / 1000),
      });
    };
  }, [pageName, trackEvent]);

  const trackInteraction = (action: string, data?: any) => {
    trackEvent(`${pageName}_${action}`, {
      page: pageName,
      ...data,
    });
  };

  return { trackInteraction };
}

// Usage
function ProductPage({ product }) {
  const { trackInteraction } = usePageAnalytics('product_detail');

  return (
    <div>
      <button
        onClick={() =>
          trackInteraction('add_to_wishlist', {
            product_id: product.id,
          })
        }
      >
        Add to Wishlist
      </button>
    </div>
  );
}

Error Handling#

Safe Event Tracking#

function useSafeEvent() {
  const trackEvent = useEvent();

  return useCallback(
    async (name: string, data?: any) => {
      try {
        await trackEvent(name, data);
      } catch (error) {
        console.error('Analytics error:', error);
        // Don't let analytics errors break the app
      }
    },
    [trackEvent],
  );
}

Provider Error Boundary#

function AnalyticsProvider({ children, config }) {
  return (
    <ErrorBoundary fallback={<>{children}</>}>
      <PulsoraProvider config={config}>{children}</PulsoraProvider>
    </ErrorBoundary>
  );
}

Testing#

Mock Hooks#

// __mocks__/@pulsora/react.ts
export const usePageview = jest.fn();
export const useEvent = jest.fn(() => jest.fn());
export const usePulsora = jest.fn(() => ({
  getVisitorFingerprint: jest.fn(() => 'test_fp'),
  getSessionId: jest.fn(() => 'test_session'),
}));

Component Tests#

import { render, fireEvent } from '@testing-library/react';
import { useEvent } from '@pulsora/react';

jest.mock('@pulsora/react');

test('tracks button click', () => {
  const mockTrackEvent = jest.fn();
  (useEvent as jest.Mock).mockReturnValue(mockTrackEvent);

  const { getByText } = render(<Button />);

  fireEvent.click(getByText('Click Me'));

  expect(mockTrackEvent).toHaveBeenCalledWith('button_click');
});

Performance#

Memoization#

function OptimizedTracking() {
  const trackEvent = useEvent();

  // Memoize event handler
  const handleClick = useCallback(() => {
    trackEvent('expensive_calculation', {
      timestamp: Date.now(),
    });
  }, [trackEvent]);

  return <ExpensiveComponent onClick={handleClick} />;
}

Debounced Tracking#

function SearchBox() {
  const trackEvent = useEvent();

  const trackSearch = useMemo(
    () =>
      debounce((query: string) => {
        trackEvent('search', { query });
      }, 1000),
    [trackEvent],
  );

  return (
    <input
      onChange={(e) => trackSearch(e.target.value)}
      placeholder="Search..."
    />
  );
}

Next Steps#