useExperienceJourney
TheuseExperienceJourney hook provides a React wrapper around the ExperienceJourney state machine for orchestrating complex consumer journeys through experiences.
Signature
Copy
function useExperienceJourney(
experienceId: string | undefined,
options?: UseExperienceJourneyOptions
): UseExperienceJourneyResult;
Parameters
| Parameter | Type | Description |
|---|---|---|
experienceId | string | undefined | The experience identifier |
options | UseExperienceJourneyOptions | Optional configuration |
Options
Copy
interface UseExperienceJourneyOptions {
/** Access code to use when starting */
accessCode?: string;
/** Automatically enter waitlist if available */
autoEnterWaitlist?: boolean;
/** Automatically start the journey when mounted */
autoStart?: boolean;
}
Return Type
Copy
interface UseExperienceJourneyResult {
/** The underlying ExperienceJourney instance */
journey: ExperienceJourney | null;
/** Current journey state */
state: ExperienceJourneyState | null;
/** Current status (derived from state) */
status: ExperienceJourneyStatus;
/** Error message if any */
error: string | null;
/** Start or restart the journey */
start: (options?: ExperienceJourneyOptions) => Promise<ExperienceJourneyState>;
}
Journey Status
Copy
type ExperienceJourneyStatus =
| "idle" // Not started
| "entering_experience" // Starting journey
| "routing_sequence" // Finding sequence
| "needs_authentication" // Auth required
| "needs_access_code" // Access code required
| "validating_access" // Validating access
| "loading_distributions" // Loading distribution data
| "entering_waitlist" // Joining waitlist
| "waiting" // On waitlist or sequence upcoming
| "ready" // Ready to participate
| "no_sequence_available" // No sequence found
| "error"; // Error state
Journey State
Copy
interface ExperienceJourneyState {
experienceId: string;
sequenceId?: string;
status: ExperienceJourneyStatus;
snapshot: JourneySnapshot;
}
Basic Usage
Copy
import { useExperienceJourney, useFanfareAuth } from "@waitify-io/fanfare-sdk-react";
function ExperiencePage({ experienceId }: { experienceId: string }) {
const { isAuthenticated, guest } = useFanfareAuth();
const { journey, state, status, error, start } = useExperienceJourney(experienceId);
const handleStart = async () => {
if (!isAuthenticated) {
await guest();
}
await start();
};
if (error) {
return <div className="error">{error}</div>;
}
return (
<div className="experience-page">
{status === "idle" && <button onClick={handleStart}>Start Experience</button>}
{status === "needs_authentication" && (
<div>
<p>Please log in to continue</p>
<LoginForm />
</div>
)}
{status === "needs_access_code" && <AccessCodeForm journey={journey!} />}
{status === "waiting" && (
<div>
<p>You are on the waitlist!</p>
<p>We will notify you when your turn comes.</p>
</div>
)}
{status === "ready" && (
<div>
<h2>You are ready!</h2>
<JourneyParticipation journey={journey!} state={state!} />
</div>
)}
</div>
);
}
Auto-Start Journey
Copy
function AutoStartExperience({ experienceId }: { experienceId: string }) {
const { status, state, error } = useExperienceJourney(experienceId, {
autoStart: true,
});
if (status === "entering_experience" || status === "routing_sequence") {
return <LoadingSpinner />;
}
if (error) {
return <ErrorDisplay message={error} />;
}
return <JourneyDisplay state={state} status={status} />;
}
With Access Code
Copy
function VIPExperience({ experienceId, accessCode }: { experienceId: string; accessCode?: string }) {
const { status, start } = useExperienceJourney(experienceId, {
accessCode,
autoStart: !!accessCode,
});
if (!accessCode && status === "idle") {
return <AccessCodeEntry onSubmit={(code) => start({ accessCode: code })} />;
}
if (status === "needs_access_code") {
return (
<div>
<p>Invalid access code. Please try again.</p>
<AccessCodeEntry onSubmit={(code) => start({ accessCode: code })} />
</div>
);
}
return <JourneyDisplay status={status} />;
}
Access Code Form
Copy
function AccessCodeForm({ journey }: { journey: ExperienceJourney }) {
const [code, setCode] = useState("");
const [error, setError] = useState<string | null>(null);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
try {
await journey.provideAccessCode(code);
} catch (err) {
setError("Invalid access code");
}
};
return (
<form onSubmit={handleSubmit}>
<h3>Enter Access Code</h3>
{error && <div className="error">{error}</div>}
<input type="text" value={code} onChange={(e) => setCode(e.target.value)} placeholder="Enter your code" />
<button type="submit">Continue</button>
</form>
);
}
Working with Journey State
Copy
function JourneyParticipation({ journey, state }: { journey: ExperienceJourney; state: ExperienceJourneyState }) {
const { snapshot } = state;
const { availableActions, context } = snapshot;
const handleAction = async (action: string) => {
await journey.perform(action);
};
return (
<div className="participation">
<p>Sequence: {context.sequenceId}</p>
<p>Distribution: {context.distribution?.type}</p>
<div className="actions">
{availableActions.sequence.includes("enter_queue") && (
<button onClick={() => handleAction("enter_queue")}>Enter Queue</button>
)}
{availableActions.sequence.includes("enter_draw") && (
<button onClick={() => handleAction("enter_draw")}>Enter Draw</button>
)}
{availableActions.sequence.includes("enter_auction") && (
<button onClick={() => handleAction("enter_auction")}>Join Auction</button>
)}
{availableActions.sequence.includes("enter_timed_release") && (
<button onClick={() => handleAction("enter_timed_release")}>Start Shopping</button>
)}
{availableActions.sequence.includes("leave_participation") && (
<button onClick={() => handleAction("leave_participation")} className="secondary">
Leave
</button>
)}
</div>
{snapshot.sequenceStage === "admitted" && context.admittanceToken && (
<div className="admitted">
<h3>You are admitted!</h3>
<a href={`/checkout?token=${context.admittanceToken}`}>Proceed to Checkout</a>
</div>
)}
</div>
);
}
Listening to State Changes
Copy
function JourneyWithStateListener({ experienceId }: { experienceId: string }) {
const { journey, status, start } = useExperienceJourney(experienceId);
const [events, setEvents] = useState<string[]>([]);
useEffect(() => {
if (!journey) return;
const unsubscribe = journey.state.listen((snapshot) => {
setEvents((prev) => [...prev, `Stage: ${snapshot.journeyStage} / ${snapshot.sequenceStage}`]);
});
return () => unsubscribe();
}, [journey]);
return (
<div>
<button onClick={() => start()}>Start</button>
<div className="event-log">
{events.map((event, i) => (
<div key={i}>{event}</div>
))}
</div>
</div>
);
}
Handling Requirements
Copy
function RequirementsHandler({ experienceId }: { experienceId: string }) {
const { journey, state, status, start } = useExperienceJourney(experienceId, {
autoStart: true,
});
const requirements = state?.snapshot.requirements || [];
// Handle authentication requirement
if (status === "needs_authentication") {
return (
<AuthenticationFlow
onComplete={async () => {
await journey?.authenticate();
}}
/>
);
}
// Handle access code requirement
if (status === "needs_access_code") {
const isRequired = requirements.find((r) => r.type === "access_code")?.required;
return (
<div>
<AccessCodeInput onSubmit={(code) => journey?.provideAccessCode(code)} />
{!isRequired && <button onClick={() => journey?.skipAccessCode()}>Skip</button>}
</div>
);
}
return <JourneyDisplay journey={journey} state={state} />;
}
Auto-Enter Waitlist
Copy
function WaitlistAutoJoin({ experienceId }: { experienceId: string }) {
const { status, state } = useExperienceJourney(experienceId, {
autoStart: true,
autoEnterWaitlist: true, // Automatically join waitlist if upcoming
});
if (status === "waiting") {
return (
<div className="waitlist-joined">
<h3>You are on the waitlist!</h3>
<p>We will notify you when the experience opens.</p>
</div>
);
}
return <JourneyDisplay status={status} state={state} />;
}
Complete Example
Copy
function CompleteExperienceFlow({ experienceId }: { experienceId: string }) {
const { isAuthenticated, isGuest, guest } = useFanfareAuth();
const { journey, state, status, error, start } = useExperienceJourney(experienceId);
const handleStart = async (accessCode?: string) => {
// Ensure some form of authentication
if (!isAuthenticated && !isGuest) {
await guest();
}
await start({ accessCode });
};
// Render based on status
const renderContent = () => {
switch (status) {
case "idle":
return (
<div className="start-section">
<h2>Welcome!</h2>
<button onClick={() => handleStart()}>Enter Experience</button>
</div>
);
case "entering_experience":
case "routing_sequence":
case "loading_distributions":
return <LoadingSpinner message="Setting up your experience..." />;
case "needs_authentication":
return <LoginPrompt onLogin={() => journey?.authenticate()} />;
case "needs_access_code":
return <AccessCodeForm onSubmit={(code) => handleStart(code)} />;
case "waiting":
return (
<WaitingView
onJoinWaitlist={() => journey?.enterWaitlist()}
isOnWaitlist={state?.snapshot.sequenceStage === "waitlist_entered"}
/>
);
case "ready":
return <ReadyView journey={journey!} state={state!} />;
case "no_sequence_available":
return <NoAccessView />;
case "error":
return <ErrorView message={error} onRetry={() => start()} />;
default:
return null;
}
};
return <div className="experience-flow">{renderContent()}</div>;
}
i18n Integration
The hook automatically loads experience-level translations:Copy
function LocalizedExperience({ experienceId }: { experienceId: string }) {
// The hook loads i18n from the experience and applies it to the I18nProvider
const { status } = useExperienceJourney(experienceId, { autoStart: true });
// Translations from the experience are now available
const t = useTranslations();
return (
<div>
<h1>{t("experience.title")}</h1>
<p>{t("experience.description")}</p>
</div>
);
}
TypeScript
Copy
import { useExperienceJourney } from "@waitify-io/fanfare-sdk-react";
import type {
ExperienceJourneyState,
ExperienceJourneyStatus,
ExperienceJourney,
UseExperienceJourneyOptions,
UseExperienceJourneyResult,
} from "@waitify-io/fanfare-sdk-react";
function TypedJourney({ experienceId }: { experienceId: string }) {
const options: UseExperienceJourneyOptions = {
autoStart: true,
autoEnterWaitlist: true,
};
const result: UseExperienceJourneyResult = useExperienceJourney(experienceId, options);
const { journey, state, status, start } = result;
return null;
}
Related
- Core SDK Experiences - Underlying journey API
- useFanfareAuth - Authentication hook
- useQueue - For queue-specific operations within a journey
- useDraw - For draw-specific operations within a journey