# React Router

URL: https://supapulse.app/docs/guides/react-router

Add Supapulse to a React Router single-page app, with automatic pageviews, events and checkout attribution.

## Install [#install]

```bash
npm install @supapulse/core @supapulse/react
```

## Wrap the app [#wrap-the-app]

```tsx title="src/main.tsx"
import { SupapulseProvider } from '@supapulse/react';
import { createRoot } from 'react-dom/client';
import { RouterProvider, createBrowserRouter } from 'react-router-dom';
import { routes } from './routes';

const router = createBrowserRouter(routes);
const config = { apiToken: import.meta.env.VITE_SUPAPULSE_TOKEN };

createRoot(document.getElementById('root')!).render(
  <SupapulseProvider config={config}>
    <RouterProvider router={router} />
  </SupapulseProvider>,
);
```

Keep the provider above the router so it isn't remounted.

## Pageviews [#pageviews]

React Router navigates with `history.pushState`, which the tracker follows. Every route change is a pageview without extra code.

To control pageviews yourself, for example to send a cleaned URL, turn them off and use the hook inside the router:

```tsx
const config = { apiToken: import.meta.env.VITE_SUPAPULSE_TOKEN, autoPageviews: false };

function RouteTracker() {
  const location = useLocation();
  usePageview({ trigger: location.pathname });
  return null;
}
```

Render `<RouteTracker />` in your root layout route.

## Events [#events]

```tsx
import { useEvent } from '@supapulse/react';

export function PricingPage() {
  const trackEvent = useEvent();
  return <button onClick={() => trackEvent('plan_selected', { plan: 'growth' })}>Choose Growth</button>;
}
```

## Checkout [#checkout]

```tsx
import { useSupapulse } from '@supapulse/react';

export function BuyButton({ priceId }: { priceId: string }) {
  const supapulse = useSupapulse();

  async function buy() {
    const response = await fetch('/api/checkout', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        priceId,
        fingerprint: await supapulse.getVisitorFingerprint(),
        sessionId: supapulse.getSessionId(),
      }),
    });
    window.location.href = (await response.json()).url;
  }

  return <button onClick={buy}>Buy</button>;
}
```

Your backend stores the fingerprint and session ID in the payment's metadata, next to your user ID. With Stripe, Supapulse reads them from its own webhook. See the [Stripe guide](https://supapulse.app/docs/guides/stripe); other processors are in [Payment webhooks](https://supapulse.app/docs/revenue/webhooks).

## Troubleshooting [#troubleshooting]

- **Two pageviews per navigation.** Some setups call `replaceState` right after `pushState` (for example, scroll restoration or search-param syncing). Use `autoPageviews: false` with the hook above.
- **Hash routers.** With `createHashRouter` the route lives after `#`, and Supapulse records the URL's path, so every page shows up as `/`. Use `autoPageviews: false` and send the route as the URL yourself: `supapulse.pageview({ url: location.origin + location.pathname })` with React Router's `location`.
