Installation#

@pulsora/react provides React hooks and components for seamless Pulsora integration.

Prerequisites#

  • React 16.8 or higher (hooks support)
  • @pulsora/core package

NPM Installation#

1. Install Both Packages#

npm install @pulsora/react @pulsora/core

Or using Yarn:

yarn add @pulsora/react @pulsora/core

Or using pnpm:

pnpm add @pulsora/react @pulsora/core

2. Set Up Provider#

Wrap your app with PulsoraProvider:

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

function App() {
  return (
    <PulsoraProvider config={{ apiToken: 'pub_your_token' }}>
      <YourApp />
    </PulsoraProvider>
  );
}

3. Use Hooks#

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

function MyComponent() {
  usePageview(); // Automatic pageview tracking
  const trackEvent = useEvent();

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

TypeScript Support#

Full TypeScript support is included:

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

const config: PulsoraConfig = {
  apiToken: process.env.NEXT_PUBLIC_PULSORA_TOKEN!,
  debug: process.env.NODE_ENV === 'development',
};

function App() {
  return (
    <PulsoraProvider config={config}>
      <YourApp />
    </PulsoraProvider>
  );
}

Framework Setup#

Create React App#

// src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { PulsoraProvider } from '@pulsora/react';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <PulsoraProvider config={{ apiToken: 'your-token' }}>
      <App />
    </PulsoraProvider>
  </React.StrictMode>,
);

Next.js App Router#

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

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

function PageviewTracker() {
  const pathname = usePathname();
  usePageview({ trigger: pathname });
  return null;
}

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

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

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

Next.js Pages Router#

// pages/_app.tsx
import type { AppProps } from 'next/app';
import { PulsoraProvider } from '@pulsora/react';
import { useRouter } from 'next/router';
import { usePageview } from '@pulsora/react';

function PageviewTracker() {
  const router = useRouter();
  usePageview({ trigger: router.asPath });
  return null;
}

export default function App({ Component, pageProps }: AppProps) {
  return (
    <PulsoraProvider
      config={{
        apiToken: process.env.NEXT_PUBLIC_PULSORA_TOKEN!,
      }}
    >
      <PageviewTracker />
      <Component {...pageProps} />
    </PulsoraProvider>
  );
}

Remix#

// app/root.tsx
import {
  Links,
  LiveReload,
  Meta,
  Outlet,
  Scripts,
  ScrollRestoration,
  useLocation,
} from '@remix-run/react';
import { PulsoraProvider, usePageview } from '@pulsora/react';

function Document({ children }: { children: React.ReactNode }) {
  const location = useLocation();
  usePageview({ trigger: location.pathname });

  return (
    <html lang="en">
      <head>
        <Meta />
        <Links />
      </head>
      <body>
        {children}
        <ScrollRestoration />
        <Scripts />
        <LiveReload />
      </body>
    </html>
  );
}

export default function App() {
  return (
    <PulsoraProvider
      config={{
        apiToken: process.env.PULSORA_TOKEN!,
      }}
    >
      <Document>
        <Outlet />
      </Document>
    </PulsoraProvider>
  );
}

Gatsby#

// gatsby-browser.js & gatsby-ssr.js
import React from 'react';
import { PulsoraProvider } from '@pulsora/react';

export const wrapRootElement = ({ element }) => (
  <PulsoraProvider
    config={{
      apiToken: process.env.GATSBY_PULSORA_TOKEN,
    }}
  >
    {element}
  </PulsoraProvider>
);

// src/components/Layout.js
import { usePageview } from '@pulsora/react';

export default function Layout({ children }) {
  usePageview(); // Tracks on mount

  return (
    <div>
      <header>...</header>
      <main>{children}</main>
      <footer>...</footer>
    </div>
  );
}

Vite#

// src/main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { PulsoraProvider } from '@pulsora/react';
import App from './App';

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <PulsoraProvider
      config={{
        apiToken: import.meta.env.VITE_PULSORA_TOKEN,
      }}
    >
      <App />
    </PulsoraProvider>
  </React.StrictMode>,
);

Environment Variables#

Create React App#

# .env
REACT_APP_PULSORA_TOKEN=pub_your_token
<PulsoraProvider config={{
  apiToken: process.env.REACT_APP_PULSORA_TOKEN
}}>

Next.js#

# .env.local
NEXT_PUBLIC_PULSORA_TOKEN=pub_your_token
<PulsoraProvider config={{
  apiToken: process.env.NEXT_PUBLIC_PULSORA_TOKEN
}}>

Vite#

# .env
VITE_PULSORA_TOKEN=pub_your_token
<PulsoraProvider config={{
  apiToken: import.meta.env.VITE_PULSORA_TOKEN
}}>

Remix#

# .env
PULSORA_TOKEN=pub_your_token

Load in root.tsx:

export const loader = () => {
  return json({
    ENV: {
      PULSORA_TOKEN: process.env.PULSORA_TOKEN,
    },
  });
};

Configuration Options#

The PulsoraProvider accepts all @pulsora/core configuration options:

interface PulsoraConfig {
  apiToken: string; // Required: Your public API token
  endpoint?: string; // Optional: Custom API endpoint
  autoPageviews?: boolean; // Optional: Auto-track pageviews (default: true)
  debug?: boolean; // Optional: Enable debug logging (default: false)
  maxRetries?: number; // Optional: Max retry attempts (default: 10)
  retryBackoff?: number; // Optional: Initial retry delay in ms (default: 1000)
}

Development Configuration#

<PulsoraProvider config={{
  apiToken: process.env.NEXT_PUBLIC_PULSORA_TOKEN!,
  debug: process.env.NODE_ENV === 'development',
  endpoint: process.env.NODE_ENV === 'development'
    ? 'http://localhost:8000/api/ingest'
    : undefined
}}>

Production Configuration#

<PulsoraProvider config={{
  apiToken: process.env.NEXT_PUBLIC_PULSORA_TOKEN!,
  debug: false,
  autoPageviews: true
}}>

SSR Considerations#

Next.js SSR/SSG#

The package is SSR-safe and won't initialize on the server:

// This is safe in SSR/SSG
export async function getServerSideProps() {
  return {
    props: {
      // Your props
    },
  };
}

function Page() {
  usePageview(); // Only runs in browser
  return <div>...</div>;
}

Hydration Safety#

Avoid tracking during hydration:

function Component() {
  const [isHydrated, setIsHydrated] = useState(false);
  const trackEvent = useEvent();

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

  const handleClick = () => {
    if (isHydrated) {
      trackEvent('button_click');
    }
  };

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

Verification#

1. Check Provider Setup#

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

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

  useEffect(() => {
    console.log('Pulsora initialized:', pulsora.isInitialized());
  }, [pulsora]);

  return null;
}

2. Enable Debug Mode#

<PulsoraProvider config={{
  apiToken: 'your-token',
  debug: true // See console logs
}}>

3. Test Tracking#

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

  return (
    <button
      onClick={() => {
        trackEvent('test_event', { timestamp: Date.now() });
        console.log('Event tracked - check Network tab');
      }}
    >
      Test Tracking
    </button>
  );
}

Common Issues#

"usePulsora must be used within PulsoraProvider"#

Ensure your component is wrapped with PulsoraProvider:

// ❌ Wrong
function App() {
  const pulsora = usePulsora(); // Error!
  return <div>...</div>;
}

// ✅ Correct
function App() {
  return (
    <PulsoraProvider config={{ apiToken: 'token' }}>
      <MyComponent />
    </PulsoraProvider>
  );
}

function MyComponent() {
  const pulsora = usePulsora(); // Works!
  return <div>...</div>;
}

"Cannot read property 'init' of undefined"#

Check that both packages are installed:

npm list @pulsora/react @pulsora/core

Build Errors#

For TypeScript projects, ensure you have React types:

npm install --save-dev @types/react @types/react-dom

Bundle Size Impact#

The React package adds minimal overhead:

  • @pulsora/react: ~800 bytes gzipped
  • @pulsora/core: ~3KB gzipped
  • Total: ~3.8KB gzipped

Best Practices#

1. Single Provider#

Only use one PulsoraProvider at the app root:

// ✅ Good
<PulsoraProvider config={config}>
  <App />
</PulsoraProvider>

// ❌ Bad - multiple providers
<PulsoraProvider config={config1}>
  <PulsoraProvider config={config2}>
    <App />
  </PulsoraProvider>
</PulsoraProvider>

2. Environment Variables#

Always use environment variables for tokens:

// ✅ Good
apiToken: process.env.NEXT_PUBLIC_PULSORA_TOKEN;

// ❌ Bad
apiToken: 'pub_hardcoded_token';

3. Error Boundaries#

Wrap with error boundary to prevent tracking errors from crashing your app:

class AnalyticsErrorBoundary extends React.Component {
  componentDidCatch(error) {
    console.error('Analytics error:', error);
  }

  render() {
    return this.props.children;
  }
}

<AnalyticsErrorBoundary>
  <PulsoraProvider config={config}>
    <App />
  </PulsoraProvider>
</AnalyticsErrorBoundary>;

Next Steps#