Docs / SDKs View as Markdown

React

SplitHubProvider with a property, <Experiment>/<Variant>, useVariant, useValue, useTrack, useIdentify, automatic pageviews.

@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; so do React Native and Expo, see React Native and Expo.

npm i @splithub/react @splithub/core

Provider

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

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

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

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 on the provider and the same install collects pageviews and events. npx splithub analytics connect adds the prop for you.

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

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.
SplitHub
See what works. Ship what wins.