React SDK
@pivolio/react wraps @pivolio/browser in a context provider so
a React tree owns exactly one Tracker, built at the right time in the lifecycle.
On Next.js, install @pivolio/next instead — it re-exports
everything on this page and adds App Router page views.
Install
@pivolio/browser comes along as a dependency. You don’t install it separately,
and you don’t import from it — @pivolio/react re-exports the types, the enums
(StandardEventName, ConsentStatus, DeviceType), PivolioError and the
Tracker class.
Setup
Wrap your app once, as high as you can:
The provider is SSR-safe by construction: the Tracker is built inside an
effect, which never runs on the server. Server render and first client render
both see null, so the markup matches and hydration stays clean. Constructing it
during render would read cookies and mint an anonymous_id in an environment
that cannot persist one.
Why the singleton API is not re-exported
@pivolio/react deliberately does not re-export init, track, page,
identify or any other singleton function from @pivolio/browser.
Those functions operate on a module-level instance created by init(). The
provider creates its own instance with new Tracker(config). Import both into
one app and you get two independent trackers on the page: two sets of
listeners, two batch buffers, two page views per navigation, and — because each
starts its own session — attribution split across two session ids. Nothing
throws; the numbers are just quietly wrong.
Do not import { track } from '@pivolio/browser' inside a component tree that
already renders a PivolioProvider. Use useTrack() or usePivolio().
Hooks
usePivolio()
Returns the live Tracker, or null before the provider’s effect has run.
Reach for this when you need something outside the four common calls —
setConsent, optOut, getLinkerParam, getConsent, flush. Always
null-check; the first render has no tracker yet.
useTrack()
Stable callbacks for the four everyday operations:
The returned object is memoized on the tracker, so it’s safe in a useEffect
dependency array. Calls made before the tracker exists are dropped, not
queued — which matters mainly for tracking inside a mount effect on the very
first render.
usePageView()
Fires one page view once the tracker is ready:
Client-only, since effects don’t run on the server. For a router-driven app, prefer wiring page views to your router’s location rather than calling this in every page component.
Config identity and re-renders
The provider rebuilds its Tracker only when writeKey or apiUrl change.
Every other option is read through a ref.
That’s what makes the obvious call site safe:
An inline object literal is a new object on every parent render. If the effect
keyed on the object’s identity, each parent render would tear down the tracker
and build a new one — rotating the session, re-running the /id fetch, and
re-firing a page view. Keying on the two values that genuinely identify a
workspace and collector avoids all of that, with no useMemo required of you.
The consequence is that the remaining options are init-time only. Changing
debug or consent in the config prop after mount does nothing. Change those at
runtime through the tracker: tracker.setConsent(...), tracker.debug(true).
Next steps
- Browser SDK — the full method surface on
Tracker. - Configuration — every
SDKConfigfield. - Next.js — App Router page views.