React wrapper
A provider and two hooks over the TypeScript SDK. It installs one client per page, so React strict mode does not double your events.
Where it is published
@prodantix/react on npm. It wraps @prodantix/sdk, so install both.
Install
bun add @prodantix/sdk @prodantix/react| Export | What it is |
|---|---|
| ProdantixProvider | Creates the client once per page and provides it |
| useProdantix | The client for the page, or null |
| useMessenger | A handle that opens and closes the widget, or null |
| prodantixConfig | The config when both key and host are present, else null |
| skipReason | Why this mount will send nothing, or null |
| resetProdantixForTest | Clears the once-per-page install, for tests |
The provider
Mount ProdantixProvider once, near the root. It creates the web client on the first mount and reuses it after that, because creating a second one would patch the History API twice and double every autocaptured event.
import { ProdantixProvider } from '@prodantix/react';
export const Providers = ({ children }: { children: ReactNode }) => (
<ProdantixProvider
options={{
apiKey: process.env.NEXT_PUBLIC_PRODANTIX_KEY,
host: 'https://eu.api.prodantix.com',
messengerHost: 'https://eu.edge.prodantix.com',
}}
>
{children}
</ProdantixProvider>
);Naming the signed-in person
person tells the provider who is signed in, as a prop rather than a call, so sign-in and sign-out reach the client as the values they are. It has three states and each says something different. undefined means this surface does not know yet, because its auth is still hydrating, and the provider does nothing. A value identifies: the provider calls identify on the first one and again whenever distinctId changes, carrying the SDK's own traits, avatar, email, name and phone. null means the surface consulted a session and found nobody, and the provider resets the client if, and only if, it is still identified, which rotates the anonymous id so the next visitor on that browser is not the person who signed out. That rule is what makes sign-out hold where signing out is a hard navigation and no value-to-null transition ever renders. It is also why null must never stand for loading: a signed-in person reloading the page would be reset before auth hydrates. A mount that consults no session at all, a landing page or a docs page, passes undefined, and an absent prop already reads as undefined.
import { ProdantixProvider } from '@prodantix/react';
export const Providers = ({ children }: { children: ReactNode }) => {
const { session, status } = useSession();
// undefined while auth hydrates, null once it has resolved to nobody,
// the subject once it has resolved to someone.
const person = status === 'loading' ? undefined : session ? { distinctId: session.sub } : null;
return (
<ProdantixProvider
options={{
apiKey: process.env.NEXT_PUBLIC_PRODANTIX_KEY,
host: 'https://eu.api.prodantix.com',
person,
}}
>
{children}
</ProdantixProvider>
);
};The hooks
useProdantix returns the client for the page, or null on the server, before the effect has run, and outside a provider. useMessenger returns a handle that opens and closes the widget, and stays null until the project configuration has been read and says enabled.
import { useMessenger, useProdantix } from '@prodantix/react';
export const HelpButton = () => {
const prodantix = useProdantix();
const messenger = useMessenger();
// Both are null until the provider effect has run, so neither is assumed.
return (
<button
onClick={() => {
prodantix?.capture('help.opened');
messenger?.open();
}}
type="button"
>
Get help
</button>
);
};When it sends nothing
An app ships the same way with Prodantix switched off, so a missing key or host is not an error. prodantixConfig tells you whether both values are present, and skipReason says in one line why a mount will send nothing, rather than returning silently.
import { prodantixConfig, skipReason } from '@prodantix/react';
const env = {
host: process.env.NEXT_PUBLIC_PRODANTIX_HOST,
key: process.env.NEXT_PUBLIC_PRODANTIX_KEY,
};
// null when either value is missing: the app runs with Prodantix switched off.
if (prodantixConfig(env) === null) {
console.warn(skipReason(env, null));
}