> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fanfare.io/llms.txt
> Use this file to discover all available pages before exploring further.

# React Custom UI Example

> Build a React UI from useExperienceJourney.

Use `useExperienceJourney` when you want React state management from the adapter but your own markup for each journey state.

```tsx theme={null}
import type { JourneyView } from "@fanfare-io/fanfare-sdk-core/experiences";
import { useExperienceJourney } from "@fanfare-io/fanfare-sdk-react";

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

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

  if (!view) {
    return <LoadingPanel />;
  }

  return (
    <section>
      <h1>Limited release</h1>
      <JourneyPanel view={view} start={start} />
    </section>
  );
}

function JourneyPanel({
  view,
  start,
}: {
  view: JourneyView;
  start: (options?: { accessCode?: string; autoEnterWaitlist?: boolean }) => Promise<JourneyView>;
}) {
  if (view.journeyStage === "ready") {
    return <button onClick={() => void start()}>Enter</button>;
  }

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

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

  if (view.sequence.phase === "granted") {
    return <CheckoutPanel onCheckout={() => view.sequence.claim().token} />;
  }

  return <SequencePanel sequence={view.sequence} />;
}
```

Call `claim()` to begin checkout: it returns the `AdmissionGrant` (use `grant.token` for handoff) and starts checkout, stopping the client-side grant-expiry timer so the server reservation owns the deadline. An unclaimed grant lapses to an `ended` sequence with an `"expired"` outcome.

## Gate handling

Your `GatePanel` should complete the user action, then call `reroute()`.

```tsx theme={null}
async function applyGateAccessCode(view: Extract<JourneyView, { journeyStage: "gated" }>, code: string) {
  await view.reroute({ accessCode: code });
}
```

The routed-stage equivalent is `view.submitAccessCode(code)`, which returns an `AccessCodeSubmitResult` instead of rerouting the gate.

Use generic customer-facing copy for gates. Do not describe private enforcement or routing logic.
