@pulsora/react#

The official React package for Pulsora Analytics. Provides hooks and components for idiomatic React integration with automatic pageview tracking and event handling.

When to Use React Package#

Choose @pulsora/react when you:

  • Build React applications - Get idiomatic React hooks (usePageview, useEvent, etc.)
  • Use Next.js, Remix, or Gatsby - SSR-compatible out of the box
  • Want automatic tracking - Pageviews tracked on component mount
  • Prefer declarative code - React hooks instead of imperative calls
  • Need type safety - Full TypeScript support with React types
  • Use React Router - Seamless integration with any router

Use @pulsora/core instead if you're not using React or need maximum flexibility - see Core vs React comparison.

Hook Comparison#

Hook Purpose When to Use Returns
usePageview() Track pageviews Component mount, route changes void
useEvent() Track custom events User interactions (clicks, forms) (name, props) => void
usePulsora() Access tracker Advanced use cases (fingerprint, session) PulsoraCore instance

Quick example:

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

function Dashboard() {
  // Track pageview on mount
  usePageview();

  // Get event tracking function
  const trackEvent = useEvent();

  return <button onClick={() => trackEvent('export_data')}>Export</button>;
}

Features#

⚛️ React-First Design#

  • Custom hooks - usePageview, useEvent, usePulsora
  • Context provider - Share tracker instance across components
  • SSR compatible - Works with Next.js, Remix, and Gatsby
  • TypeScript support - Full type safety

🚀 Zero Configuration#

  • Automatic pageview tracking - Works with any router
  • Smart re-renders - Optimized for performance
  • Error boundaries - Graceful error handling
  • Tree-shakeable - Only bundle what you use

🎯 Developer Experience#

  • Familiar patterns - Standard React hooks API
  • Framework agnostic - Works with any React setup
  • Hot reload friendly - Maintains state during development
  • Tiny bundle - <1KB gzipped

Installation#

npm install @pulsora/react @pulsora/core

Both packages are required:

  • @pulsora/react - React hooks and components
  • @pulsora/core - Core tracking functionality

Quick Start#

1. Add the Provider#

Wrap your app with PulsoraProvider:

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

function App() {
  return (
    <PulsoraProvider config={{ apiToken: 'your-api-token' }}>
      <YourApp />
    </PulsoraProvider>
  );
}

2. Track Pageviews#

Use the usePageview hook:

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

function MyPage() {
  usePageview(); // Tracks on mount

  return <div>Your content</div>;
}

3. Track Events#

Use the useEvent hook:

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

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

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

Hooks Reference#

usePageview#

Automatically tracks pageviews with router integration support.

// Basic usage - tracks on mount
usePageview();

// With router integration
const location = useLocation();
usePageview({ trigger: location.pathname });

// Conditional tracking
usePageview({ disabled: !userConsent });

useEvent#

Returns a function to track custom events.

const trackEvent = useEvent();

// Simple event
trackEvent('video_play');

// Event with properties
trackEvent('purchase', {
  product: 'Premium Plan',
  price: 99,
  currency: 'USD',
});

usePulsora#

Access the underlying Pulsora instance.

const pulsora = usePulsora();

// Access advanced features
const fingerprint = await pulsora.getVisitorFingerprint();
const sessionId = pulsora.getSessionId();

Router Integration#

React Router#

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

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

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

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

Next.js App Router#

'use client';

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

export function Analytics({ children }) {
  const pathname = usePathname();

  usePageview({ trigger: pathname });

  return <>{children}</>;
}

Next.js Pages Router#

import { useRouter } from 'next/router';
import { usePageview } from '@pulsora/react';

function MyApp({ Component, pageProps }) {
  const router = useRouter();

  usePageview({ trigger: router.asPath });

  return <Component {...pageProps} />;
}

Remix#

import { useLocation } from '@remix-run/react';
import { usePageview } from '@pulsora/react';

export function Root() {
  const location = useLocation();

  usePageview({ trigger: location.pathname });

  return <Outlet />;
}

Advanced Usage#

Custom Provider Configuration#

<PulsoraProvider
  config={{
    apiToken: process.env.REACT_APP_PULSORA_TOKEN,
    endpoint: 'https://custom.endpoint.com',
    debug: process.env.NODE_ENV === 'development',
    autoPageviews: false, // Disable if using manual tracking
  }}
>
  <App />
</PulsoraProvider>

Event Tracking Patterns#

function SearchForm() {
  const trackEvent = useEvent();
  const [query, setQuery] = useState('');

  // Track search submissions
  const handleSubmit = (e) => {
    e.preventDefault();
    trackEvent('search', {
      query,
      results_count: results.length,
      filters: activeFilters,
    });
  };

  // Track search interactions
  const handleFilterChange = (filter) => {
    trackEvent('search_filter_applied', {
      filter_type: filter.type,
      filter_value: filter.value,
    });
  };

  return <form onSubmit={handleSubmit}>{/* Form content */}</form>;
}

Error Boundaries#

import { ErrorBoundary } from 'react-error-boundary';
import { useEvent } from '@pulsora/react';

function ErrorFallback({ error, resetErrorBoundary }) {
  const trackEvent = useEvent();

  useEffect(() => {
    trackEvent('error_boundary_triggered', {
      error: error.message,
      stack: error.stack,
    });
  }, [error, trackEvent]);

  return (
    <div role="alert">
      <p>Something went wrong:</p>
      <pre>{error.message}</pre>
      <button onClick={resetErrorBoundary}>Try again</button>
    </div>
  );
}

function App() {
  return (
    <ErrorBoundary FallbackComponent={ErrorFallback}>
      <PulsoraProvider config={{ apiToken: 'token' }}>
        <YourApp />
      </PulsoraProvider>
    </ErrorBoundary>
  );
}

TypeScript#

Full TypeScript support with exported types:

import type {
  PulsoraConfig,
  UsePageviewOptions,
  TrackEventFunction,
} from '@pulsora/react';

// Typed event tracking
const trackEvent: TrackEventFunction = useEvent();

trackEvent<{ productId: string; price: number }>('purchase', {
  productId: 'sku_123',
  price: 99.99,
});

// Typed configuration
const config: PulsoraConfig = {
  apiToken: process.env.REACT_APP_PULSORA_TOKEN!,
  debug: true,
};

Best Practices#

1. Single Provider#

Only use one PulsoraProvider at the root of your app.

2. Route-Level Tracking#

Track pageviews at the router level, not in individual pages.

3. Meaningful Event Names#

Use descriptive, consistent event names following a naming convention.

4. Avoid Over-Tracking#

Track meaningful interactions, not every click.

SSR Considerations#

The package is SSR-safe and will only initialize in the browser:

// This is safe in SSR environments
function Page() {
  usePageview(); // No-op on server
  const trackEvent = useEvent(); // Returns no-op function on server

  return <div>Content</div>;
}

Bundle Size#

  • @pulsora/react: ~2KB uncompressed, ~743 bytes gzipped
  • @pulsora/core: ~8KB uncompressed, ~3KB gzipped
  • Total: <4KB gzipped for complete analytics

Real-World Examples#

Next.js 14 App Router Integration#

// app/providers.tsx
'use client';

import { PulsoraProvider } from '@pulsora/react';
import { usePathname, useSearchParams } from 'next/navigation';
import { usePageview } from '@pulsora/react';
import { useEffect } from 'react';

function PageviewTracker() {
  const pathname = usePathname();
  const searchParams = useSearchParams();

  usePageview({
    trigger: `${pathname}${searchParams.toString() ? `?${searchParams}` : ''}`
  });

  return null;
}

export function Providers({ children }) {
  return (
    <PulsoraProvider
      config={{
        apiToken: process.env.NEXT_PUBLIC_PULSORA_TOKEN!,
        debug: process.env.NODE_ENV === 'development'
      }}
    >
      <PageviewTracker />
      {children}
    </PulsoraProvider>
  );
}

// app/layout.tsx
import { Providers } from './providers';

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}

SaaS Dashboard with Feature Tracking#

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

function Dashboard({ user }) {
  const trackEvent = useEvent();

  // Track feature usage
  const handleExportData = async (format) => {
    trackEvent('data_export_started', {
      format,
      plan: user.plan,
      data_size: dataRows.length,
    });

    try {
      await exportData(format);
      trackEvent('data_export_completed', { format });
    } catch (error) {
      trackEvent('data_export_failed', {
        format,
        error: error.message,
      });
    }
  };

  // Track filter interactions
  const handleFilterChange = (filters) => {
    trackEvent('dashboard_filter_applied', {
      filter_types: Object.keys(filters),
      filter_count: Object.keys(filters).length,
    });
  };

  return (
    <div>
      <button onClick={() => handleExportData('csv')}>Export CSV</button>
      <FilterPanel onChange={handleFilterChange} />
    </div>
  );
}

E-commerce Checkout Flow#

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

function CheckoutPage({ cart }) {
  const trackEvent = useEvent();
  const pulsora = usePulsora();

  // Track checkout step views
  usePageview();

  useEffect(() => {
    trackEvent('checkout_started', {
      cart_value: cart.total,
      item_count: cart.items.length,
    });
  }, []);

  // Send fingerprint with payment
  const handlePayment = async (paymentMethod) => {
    trackEvent('payment_method_selected', {
      method: paymentMethod,
    });

    // Get visitor data for server-side attribution
    const fingerprint = await pulsora.getVisitorFingerprint();
    const sessionId = pulsora.getSessionId();

    try {
      const response = await fetch('/api/checkout', {
        method: 'POST',
        body: JSON.stringify({
          cart: cart.items,
          payment_method: paymentMethod,
          visitor_fingerprint: fingerprint,
          session_id: sessionId,
        }),
      });

      if (response.ok) {
        trackEvent('purchase_completed', {
          order_id: response.data.orderId,
          value: cart.total,
        });
      }
    } catch (error) {
      trackEvent('purchase_failed', {
        error: error.message,
      });
    }
  };

  return <div>{/* Checkout form */}</div>;
}

Form Tracking with Validation#

import { useEvent } from '@pulsora/react';
import { useForm } from 'react-hook-form';

function ContactForm() {
  const trackEvent = useEvent();
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm();

  // Track form start
  const handleFormFocus = () => {
    trackEvent('form_started', {
      form_type: 'contact',
    });
  };

  // Track validation errors
  useEffect(() => {
    if (Object.keys(errors).length > 0) {
      trackEvent('form_validation_failed', {
        form_type: 'contact',
        fields: Object.keys(errors),
        error_count: Object.keys(errors).length,
      });
    }
  }, [errors, trackEvent]);

  // Track successful submission
  const onSubmit = async (data) => {
    trackEvent('form_submitted', {
      form_type: 'contact',
      fields: Object.keys(data),
    });

    try {
      await submitForm(data);
      trackEvent('form_submission_success', {
        form_type: 'contact',
      });
    } catch (error) {
      trackEvent('form_submission_failed', {
        form_type: 'contact',
        error: error.message,
      });
    }
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)} onFocus={handleFormFocus}>
      <input {...register('email', { required: true })} />
      <textarea {...register('message', { required: true })} />
      <button type="submit">Send</button>
    </form>
  );
}

Troubleshooting#

Hook Rules Violations#

Error: Invalid hook call. Hooks can only be called inside of the body of a function component.

Cause: Using hooks outside React components

Solution:

// ❌ Wrong - hook used outside component
const trackEvent = useEvent();

function MyComponent() {
  return <button onClick={() => trackEvent('click')}>Click</button>;
}

// ✅ Correct - hook used inside component
function MyComponent() {
  const trackEvent = useEvent();
  return <button onClick={() => trackEvent('click')}>Click</button>;
}

Provider Missing Error#

Error: usePulsora must be used within a PulsoraProvider

Cause: Using hooks before PulsoraProvider is mounted

Solution:

// ❌ Wrong - no provider
function App() {
  usePageview(); // Error!
  return <div>App</div>;
}

// ✅ Correct - wrap with provider
function App() {
  return (
    <PulsoraProvider config={{ apiToken: 'pub_xxx' }}>
      <Dashboard />
    </PulsoraProvider>
  );
}

function Dashboard() {
  usePageview(); // Works!
  return <div>Dashboard</div>;
}

Duplicate Pageviews in SPA#

Symptoms: Multiple pageview events for the same page

Cause: Using usePageview() in multiple components or with wrong trigger

Solutions:

  1. Use at router level only:
// ❌ Wrong - multiple usePageview calls
function App() {
  usePageview(); // Called once
  return <Dashboard />;
}

function Dashboard() {
  usePageview(); // Called again - duplicate!
  return <div>Dashboard</div>;
}

// ✅ Correct - single usePageview at root
function App() {
  const location = useLocation();
  usePageview({ trigger: location.pathname });
  return <Routes>...</Routes>;
}
  1. Use trigger prop correctly:
// Pageview tracked only when pathname changes
const pathname = usePathname();
usePageview({ trigger: pathname });

SSR Hydration Errors#

Error: Warning: Expected server HTML to contain...

Cause: Client-side tracking during server render

Solution:

// The hooks are already SSR-safe, but if you need custom logic:
function Analytics() {
  const [isMounted, setIsMounted] = useState(false);

  useEffect(() => {
    setIsMounted(true);
  }, []);

  usePageview(); // Safe - no-op on server

  if (!isMounted) return null;

  return <AnalyticsComponents />;
}

Next.js Environment Variable Issues#

Error: apiToken is undefined

Cause: Environment variable not prefixed with NEXT_PUBLIC_

Solution:

# ❌ Wrong - not available in browser
PULSORA_TOKEN=pub_xxx

# ✅ Correct - available in browser
NEXT_PUBLIC_PULSORA_TOKEN=pub_xxx
// Access in component
<PulsoraProvider
  config={{
    apiToken: process.env.NEXT_PUBLIC_PULSORA_TOKEN
  }}
>

TypeScript Type Errors#

Error: Type 'string | undefined' is not assignable to type 'string'

Solution:

// Option 1: Use non-null assertion if you're certain it exists
<PulsoraProvider
  config={{
    apiToken: process.env.NEXT_PUBLIC_PULSORA_TOKEN!
  }}
>

// Option 2: Provide fallback
<PulsoraProvider
  config={{
    apiToken: process.env.NEXT_PUBLIC_PULSORA_TOKEN || ''
  }}
>

// Option 3: Type guard
const token = process.env.NEXT_PUBLIC_PULSORA_TOKEN;
if (!token) throw new Error('PULSORA_TOKEN required');

<PulsoraProvider config={{ apiToken: token }}>

Next Steps#