# React

`@splithub/react` 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).

```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}>
    <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. Props: `config`, `visitorId?` (SSR: pass the id from the cookie so server and client agree), `overrides?`, `client?`, `pageviews?`, `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()` | `(goal, props?) => void` |
| `usePageview(path)` | fires `pageview` goals on client-side navigation |
| `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 on mount for the current path; in SPAs call `usePageview(location.pathname)` once in the layout. Click goals are bound automatically from the config's selectors.

## 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.
