# Hooks reference

URL: https://supapulse.app/docs/react/hooks

useSupapulse, useEvent, useIdentify and usePageview from @supapulse/react, with their exact behavior and the edge cases.

All hooks must be called inside a [`<SupapulseProvider>`](https://supapulse.app/docs/react/provider). Outside one, `useSupapulse` (and the hooks built on it) throw `[Supapulse React] useSupapulse must be used within a SupapulseProvider`.

## useSupapulse [#usesupapulse]

```ts
function useSupapulse(): SupapulseCore;
```

Returns the provider's tracker, with every [core method](https://supapulse.app/docs/core/api-reference): `event`, `pageview`, `getSessionId` and so on.

The tracker exists from the first render, but the provider calls `init()` in an effect. A call made before that is dropped, with a `[Supapulse] Not initialized` warning in the console. Event handlers run after init and are safe. Code that runs in a mount effect should wait for `isReady` from the context:

```tsx
import { SupapulseContext, useSupapulse } from '@supapulse/react';
import { useContext, useEffect } from 'react';

function TrackOnMount({ name }: { name: string }) {
  const supapulse = useSupapulse();
  const isReady = useContext(SupapulseContext)?.isReady;

  useEffect(() => {
    if (!isReady) return;
    supapulse.event(name);
  }, [supapulse, isReady, name]);

  return null;
}
```

A child's mount effect runs before the provider's, so without the `isReady` check the first call would be dropped.

Reading the visitor fingerprint and session ID for checkout:

```tsx
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(),
      }),
    });
    location.href = (await response.json()).url;
  }

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

## useEvent [#useevent]

```ts
function useEvent(): (eventName: string, eventData?: EventData) => Promise<void>;
```

Returns a memoized function that sends a custom event.

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

Called before the tracker is ready, the function sends nothing and the tracker logs a warning. Use it from event handlers, or wait for `isReady` in effects.

## useIdentify [#useidentify]

```ts
function useIdentify(): (customerId: string) => Promise<void>;
```

Returns a memoized function that links the current visitor to your customer ID. Call it once the user is known, so revenue sent for that ID is credited to the visit that brought them. See [Revenue attribution](https://supapulse.app/docs/revenue/attribution).

```tsx
import { SupapulseContext, useIdentify } from '@supapulse/react';
import { useContext, useEffect } from 'react';

function IdentifyUser({ userId }: { userId: string | null }) {
  const identify = useIdentify();
  const isReady = useContext(SupapulseContext)?.isReady;

  useEffect(() => {
    if (isReady && userId) identify(userId);
  }, [identify, isReady, userId]);

  return null;
}
```

Identify events never count toward your usage. Like `useEvent`, calling it before the tracker is ready sends nothing.

## usePageview [#usepageview]

```ts
function usePageview(options?: { trigger?: unknown; disabled?: boolean }): void;
```

Sends pageviews from React instead of the core tracker's History API listener. It only works with `autoPageviews: false`. With the default `autoPageviews: true` the core tracker already records every pageview, so the hook does nothing.

| Option | Behavior |
| --- | --- |
| none | One pageview when the tracker becomes ready. |
| `trigger` | A pageview when the tracker becomes ready and every time `trigger` changes. Pass the path. Safe under React StrictMode. |
| `disabled: true` | Sends nothing. |

```tsx
import { useLocation } from 'react-router-dom';

const config = { apiToken: 'pub_your_token', autoPageviews: false };

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

The pageview reads `location.href` and `document.title` at the time the effect runs.

## Testing components that use the hooks [#testing-components-that-use-the-hooks]

Wrap them in the context with a fake tracker instead of the real provider:

```tsx
import { SupapulseContext } from '@supapulse/react';
import { render } from '@testing-library/react';
import { vi } from 'vitest';

const fake = {
  init: vi.fn(),
  pageview: vi.fn(),
  event: vi.fn(),
  identify: vi.fn(),
  getSessionId: () => 'test-session',
  getVisitorFingerprint: async () => null,
  use: vi.fn(),
};

render(
  <SupapulseContext.Provider value={{ supapulse: fake, isReady: true, autoPageviews: true, error: null }}>
    <PlanPicker />
  </SupapulseContext.Provider>,
);
```
