# React

`@splithub/react` collects analytics and renders experiments in any React 18+ app (Vite, CRA, Remix, Astro islands). Next.js App Router has its own package, see [Next.js](/docs/nextjs); so do React Native and Expo, see [React Native and Expo](/docs/react-native).

```bash
npm i @splithub/react @splithub/core
```

## Provider

```tsx
import { SplitHubProvider } from '@splithub/react';
import config from '../splithub.config';

createRoot(document.getElementById('root')!).render(
  <SplitHubProvider config={config} property="sh-x1h78w">
    <App />
  </SplitHubProvider>
);
```

The provider creates the client synchronously, reads or sets the `sh_vid` cookie, applies cached live overrides (cloud mode) and starts the event queue. With `property` it also records pageviews and events for that [analytics property](#analytics). Props: `config`, `property?`, `visitorId?` (SSR: pass the id from the cookie so server and client agree), `overrides?`, `client?`, `pageviews?`, `trackNavigation?`, `context?`, `remote?`, `autoGoals?`, `devtools?`.

## Component experiments

```tsx
import { Experiment, Variant } from '@splithub/react';

<Experiment id="hero-cta">
  <Variant name="control"><Hero /></Variant>
  <Variant name="bold"><HeroBold /></Variant>
</Experiment>
```

One `<Variant>` renders by assignment; the exposure is recorded on mount. Unknown or not-running experiments render the control.

## Value experiments

```tsx
const title = useValue<string>('hero-title');
const price = useValue<number>('price-anchor');
const layout = useValue<{ columns: number; dense: boolean }>('grid');
```

`useValue` returns the control's value when the experiment is not running, so the page never breaks when a test is paused.

## Hooks

| Hook | Returns |
|---|---|
| `useVariant(id)` | the variant key (`'control'`, `'bold'`…); records an exposure the first time |
| `useValue(id)` | the payload of a `value` experiment |
| `useTrack()` | `(name, props?) => void`: an event, any name |
| `useIdentify()` | `(userId, traits?) => void`: ties the visitor to your user; `null` on sign-out |
| `usePageview(path)` | reports a page yourself, for when automatic navigation tracking is off |
| `useSplitHub()` | the underlying `@splithub/core` client |

`useVariant` is for the same experiment in several places: the visitor gets one variant everywhere.

## Goals

```tsx
const track = useTrack();
return (
  <>
    <button onClick={() => track('signup')}>Sign up</button>
    <button onClick={() => track('purchase', { revenue: 49 })}>Buy</button>
  </>
);
```

Pageview goals fire for the current path on load and on every client-side navigation; there is nothing to add to your router. Click goals are bound automatically from the config's selectors.

## Analytics

Name an [analytics property](/docs/analytics) on the provider and the same install collects pageviews and events. `npx splithub analytics connect` adds the prop for you.

```tsx
<SplitHubProvider config={config} property="sh-x1h78w">
  <App />
</SplitHubProvider>
```

- **Pageviews** are automatic: one on load and one on every client-side navigation (`pushState`, `replaceState`, back and forward, hash routes), with time on page and scroll depth. To report pages yourself, pass `trackNavigation={false}` and call `usePageview(pathname)` in the layout.
- **Events**: `useTrack()` is the one call. `track('Video Played', { id: 3 })` records the event in the property and converts every experiment whose goal it is, in one row. Names are letters, digits, spaces and `_ . : / -`, up to 64 characters; props are a flat object up to 4 KB.
- **Users**: call `identify` after sign-in and with `null` on sign-out.

```tsx
import { useEffect } from 'react';
import { useIdentify } from '@splithub/react';

export function IdentifyUser({ user }: { user: { id: string; plan: string } | null }) {
  const identify = useIdentify();
  useEffect(() => {
    if (user) identify(user.id, { plan: user.plan });
    else identify(null);
  }, [identify, user]);
  return null;
}
```

Use your internal user id; keep emails and names out of traits and props. A development build (`NODE_ENV=development`) sends nothing to SplitHub Cloud: check tracking on a deploy or with `vite preview`.

## QA

Append `?sh_force=hero-cta:bold` to any URL to see a variant, running or draft; no exposure is recorded. Keep the parameter in the URL while previewing; it is not saved across navigation. Several at once: `?sh_force=hero-cta:bold,hero-title:outcome`.

To run drafts while you develop without forcing anything, set `runDraftsInDev: true` in the config (see [Drafts in development](/docs/config#drafts-in-development)). Add `?sh_debug=1`, or run `npx splithub dev`, to log every assignment with its reason and get a panel that switches variants. In Vite apps, `splithub()` from `@splithub/core/vite` prints what each experiment does in your dev terminal (see [Drafts in development](/docs/config#drafts-in-development)).

## SSR

Any framework that renders on the server: read the `sh_vid` cookie, pass it as `visitorId` to the provider on both sides, set the cookie on the response when missing. `@splithub/core` exports `newVisitorId()` and `assign()` for server-side decisions.

## Rules

- Never nest an `<Experiment>` inside a `<Variant>` of the same experiment.
- Hooks follow the rules of hooks: no conditional calls.
- Keep the config import at the top of the app; the provider must be above every component that uses a hook.
