Skip to main content
React integrations usually start with FanfareProvider and ExperienceWidget.

Install

pnpm add @fanfare-io/fanfare-sdk-core @fanfare-io/fanfare-sdk-react motion
The React package has peer dependencies on react, react-dom, and motion.

Add the provider

import { FanfareProvider } from "@fanfare-io/fanfare-sdk-react";

export function App() {
  return (
    <FanfareProvider
      organizationId="org_123"
      publishableKey="pk_live_123"
    >
      <ProductLaunchPage />
    </FanfareProvider>
  );
}
FanfareProvider initializes the core SDK, restores the local session by default, and provides the SDK to hooks and components below it.

Render the full widget

import { ExperienceWidget } from "@fanfare-io/fanfare-sdk-react";
import "@fanfare-io/fanfare-sdk-react/styles";

export function ProductLaunchPage() {
  return (
    <ExperienceWidget
      experienceId="exp_123"
      autoStart
      checkoutUrl="/checkout"
    />
  );
}
Use checkoutUrl for default granted-state navigation. Use onGranted when your application needs to coordinate custom routing or server handoff.
<ExperienceWidget
  experienceId="exp_123"
  autoStart
  onGranted={async (grant) => {
    await fetch("/api/fanfare/admission", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ grant }),
    });

    window.location.assign("/checkout");
  }}
/>
Avoid logging the grant value to third-party tools or placing it in URLs.

Build custom React UI

Use useExperienceJourney when you want React state and lifecycle handling without the default widget layout.
import { useExperienceJourney } from "@fanfare-io/fanfare-sdk-react";

export function CustomExperience({ experienceId }: { experienceId: string }) {
  const { view, start, error } = useExperienceJourney(experienceId);

  if (error) return <ErrorPanel message={error} />;
  if (!view) return <LoadingPanel />;

  if (view.journeyStage === "ready") {
    return <button onClick={() => void start()}>Start</button>;
  }

  if (view.journeyStage === "routing") {
    return <LoadingPanel />;
  }

  if (view.journeyStage === "gated") {
    return <GatePanel view={view} />;
  }

  return <SequencePanel sequence={view.sequence} />;
}
The hook returns the same public journey model as the core SDK:
FieldUse for
journeyAccess to the underlying journey handle.
viewNormal rendering and state-gated actions.
snapshotDebugging or exhaustive inspection.
errorAdapter-level setup or action errors.
start()Convenience method for starting the journey.
useExperienceJourney is the React-friendly wrapper around the underlying journey stores. It subscribes to the current view$ and snapshot$ values for you, updates React state when they change, and cleans up those subscriptions when the component unmounts. Most React UI should render from the returned view instead of subscribing to journey.view$ directly.