@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:
- 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>;
}
- 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
- Installation Guide - Detailed React setup instructions
- Hooks Reference - Complete API documentation for all hooks
- React Router Guide - React Router integration
- TypeScript Types - Type definitions and best practices
- SSR Guide - Server-side rendering with Next.js and Remix
- Examples - Copy-paste code examples for common scenarios
- Core Package - If you need non-React integration