React integration
For React single-page apps with no server of their own — Vite, Create React
App, Lovable, or anything deployed as static files. Grab your project ID
from the dashboard (Get your script) and
replace {YOUR_PROJECT_ID} below.
Using Next.js? Use the Next.js integration instead — its split-URL redirects happen on the server, before the page loads.
npm install @testa-soft/reactRequires React 18 or newer.
1. Provider
Wrap your app once, at the root:
// src/main.tsx
import { createRoot } from "react-dom/client";
import { TestaProvider } from "@testa-soft/react";
import App from "./App";
createRoot(document.getElementById("root")!).render(
<TestaProvider projectId="{YOUR_PROJECT_ID}">
<App />
</TestaProvider>,
);Done. That's a working integration.
<TestaProvider/> loads your experiments, assigns the visitor to a variation,
applies HTML changes, performs split-URL redirects, tracks goals, and reports
which variation the visitor saw. Put it above your router — React Router,
TanStack Router, Wouter and plain history.pushState all work.
HTML tests
Experiments built in the visual editor (HTML tests) apply automatically — no code. Changes are kept in place when React re-renders the element, and undone when the visitor navigates to a page the experiment doesn't target.
To stop visitors seeing the original content flash before the variant, the
provider briefly hides the page on first load until the change is applied. It
reveals itself after 4 seconds regardless, so a failed load can't leave a page
blank — and once Testa knows your project has nothing to apply, it stops hiding
altogether. Turn it off with shield={false}.
Goals
Create goals in the dashboard (Goals tracking). Page-view and click goals need no code. Custom-event goals fire from yours:
import { pushEvent } from "@testa-soft/react";
pushEvent("signup_completed", { plan: "pro" });Also window.testa.pushEvent(...) and window.Analytica.pushEvent(...). Each
goal counts once per visitor.
Analytics events
Whenever a visitor is shown a variation, Testa fires variation_applied in the
browser — subscribe to forward it wherever your analytics live.
Subscribe from a component, in an effect. onVariationApplied returns its own
unsubscribe function, so returning it from the effect is the whole cleanup —
without that, Fast Refresh and StrictMode's double mount stack duplicate
handlers:
// src/components/TestaAnalytics.tsx
import { onVariationApplied } from "@testa-soft/react";
import { useEffect } from "react";
export function TestaAnalytics(): null {
useEffect(
() =>
onVariationApplied((data) => {
// Send `data` wherever your analytics live.
console.log(data);
// {
// project_id: 123, // your project ID
// experiment: 456, // experiment ID
// variation: 1, // variation ID (0 = control)
// uuid: "0198f2c1-4b7a-7f3e-9d21-...", // the visitor's unique ID
// title: "Homepage Hero Test", // experiment name, when set
// url: "https://example.com/pricing", // page the variation applied on
// }
}),
[],
);
return null;
}Render it once, anywhere inside <TestaProvider/>.
experiment and variation are the identifiers you see in the dashboard —
variation 0 is always the control. title is present only when the experiment
has a name. Forward the whole object, or pick fields out of it:
// `track` here is whatever you already use — your analytics SDK, or your own fetch.
useEffect(
() =>
onVariationApplied((data) =>
track("Experiment Viewed", {
experiment: data.experiment,
variation: data.variation,
name: data.title,
}),
),
[],
);
// Or forward the payload as it comes.
useEffect(() => onVariationApplied((data) => track("Experiment Viewed", data)), []);Register as many handlers as you like. Two guarantees make this safe to wire into a tag manager:
- A handler registered after the event fired still receives it, so a slow-loading analytics script never misses an exposure.
- Each handler sees a given experiment/variation once, so re-renders and client-side navigations can't double-count.
Outside a bundle — GTM Custom HTML, or any plain <script> — use the global
instead. (window.testa is also importable as import { testa } from "@testa-soft/react" if you prefer a namespace to named imports; it is the same
two functions.)
<script>
window.testa.onVariationApplied(function (d) {
// send d to your analytics
});
</script>GTM dataLayer — pushed automatically on every exposure, no setup:
window.dataLayer.push({
event: "Analytica", // the trigger name
ExperimentId: 456,
ExperimentName: "Homepage Hero Test", // "" when the experiment has no name
VariationId: 1,
VariationName: "Variation1", // "Control" for variation 0
});Add a Custom Event trigger on Analytica.
The bucketing moment
onVariationAssigned fires when the visitor is bucketed, with the same
payload and the same subscribe-in-an-effect shape:
import { onVariationAssigned } from "@testa-soft/react";
useEffect(() => onVariationAssigned((data) => track("Experiment Assigned", data)), []);It fires at decision time, before a split-URL redirect leaves the page — so
it's the only event a visitor who gets redirected triggers on the original page.
variation_applied is the one to use for exposure tracking: it fires once the
visitor actually sees the variation, on the page they end up on.
Options
<TestaProvider
projectId="{YOUR_PROJECT_ID}"
cookieDomain=".example.com" // share assignments across subdomains
shield={false} // don't hide the page while variants load
>| Prop | Default | Description |
|---|---|---|
projectId | — | Your project ID. |
shield | true | Hide the page until HTML changes are applied. false to disable. |
cookieDomain | — | Cookie domain, e.g. .example.com, to keep assignments across subdomains. |
secureCookies | true | Set to false when testing on plain http://localhost. |
tracking | true | Report exposures to Testa. false to test without affecting results. |