Saltar al contenido principal

<RiftEvent>

Scopes a subtree to a single event. Owns the eventId, the lazily-loaded Stripe handle, AND the ticket-type selection state every descendant reads. Mount inside <RiftProvider> around any event-specific UI.

Selection lives here (not inside <AvailabilityList>) so the picker and its reserve action can be siblings — sticky bottom CTA, two-column picker + persistent summary, separate panels with intermediate content. Any layout that keeps everything under one <RiftEvent> works.

Import

import { RiftEvent } from "@feelrift/react";

Basic usage

import { EventHeader, RiftEvent, RiftProvider } from "@feelrift/react";

<RiftProvider>
<RiftEvent eventId="evt_summerfest_2026">
<EventHeader />
</RiftEvent>
</RiftProvider>;

Props

NameTypeRequiredDefaultDescription
eventIdstringYesevt_… prefixed event ID from the Experiences API.
childrenReactNodeYesSubtree that consumes the event scope.

eventId

The event the surrounded UI displays and acts on. Validated server-side on every request; the SDK doesn't validate the prefix client-side, so a typo surfaces as a 404 on the first config call.

Render output

Renders no DOM wrapper of its own. Provides two contexts to the subtree:

  • RiftEventContexteventId and the Stripe Promise.
  • AvailabilitySelectionContext — the selection map and its helpers (setQuantity, clearSelection, toReservationItems). Consumed via useAvailabilitySelection().

Behavior

  • Event-scoped selection. Selection state is a ReadonlyMap<ticketTypeId, quantity> plus the three helpers above. Any descendant — <TicketRow>, <ReserveButton>, custom carts, programmatic reset buttons — calls useAvailabilitySelection() to read or mutate. Layout depth doesn't matter; siblings, deep nests, slot-composed wrappers all see the same scope.
  • Per-event isolation. Mount two <RiftEvent> instances on the same page with different eventIds and each gets its own isolated selection. Mutating one doesn't affect the other.
  • Selection is ephemeral. Lives in React state, not the persisted store. Reservations supersede it — once <ReserveButton> succeeds, the local map is no longer authoritative and consumers typically call clearSelection() in the success callback.
  • Eager Stripe.js load. When useTicketingConfig() populates the cache and ticketingConfig.stripe is set, this component dynamically imports @stripe/stripe-js, initializes it with the returned publishable key plus { stripeAccount: ticketingConfig.stripe.connectedAccountId }, and stashes the resolved Promise in React state. <CheckoutForm> consumes it via context so Stripe Elements mounts without a separate loader call.
  • Free or disabled Ticketing skips the import. When ticketingConfig.stripe === null, loadStripe is never called and the optional Stripe peers don't need to be installed.
  • Stripe peer missing. If loadStripe() throws because the peer isn't installed, the component logs a warning to the console and continues — free-event embeds keep working; paid embeds need the peers (see Installation → Paid events).
  • Promise is component-local. The Stripe Promise lives in React state, not the persisted store, because it has no JSON-serializable representation and never needs to survive a reload — Stripe.js re-downloads on each session anyway.
  • Multiple instances allowed. Mounting more than one <RiftEvent> in the same <RiftProvider> scopes each subtree to its own event ID. Reservation, checkout, and config state are per-event, so this works for multi-event pickers without collision.

Examples

Two events side-by-side

<RiftProvider>
<section>
<RiftEvent eventId="evt_summerfest_2026">
<EventHeader />
<AvailabilityList />
</RiftEvent>
</section>
<section>
<RiftEvent eventId="evt_winterfest_2026">
<EventHeader />
<AvailabilityList />
</RiftEvent>
</section>
</RiftProvider>

Each section maintains its own reservation and checkout state.

Dynamic event ID from a router

function EventPage({ params }: { params: { eventId: string } }) {
return (
<RiftEvent eventId={params.eventId}>
<EventHeader />
<AvailabilityList />
<ReserveButton requireCaptcha />
<CheckoutForm returnUrl="https://your.site/checkout/return" />
</RiftEvent>
);
}

Two-panel layout with a sticky reserve action

Picker and reserve action live in distinct visual surfaces. Both read from the same <RiftEvent> selection scope — no extra wrapping needed.

<RiftEvent eventId="evt_summerfest_2026">
<main className="scrollable-tickets">
<AvailabilityList />
</main>
<footer className="sticky-cta">
<ReservationSummary />
<ReserveButton requireCaptcha />
</footer>
</RiftEvent>

Custom selection consumer alongside the picker

A "selection so far" badge that updates as the user picks tickets, sitting outside <AvailabilityList>:

import { useAvailabilitySelection } from "@feelrift/react";

function SelectionBadge() {
const { selection } = useAvailabilitySelection();
const total = Array.from(selection.values()).reduce((a, b) => a + b, 0);
return total > 0 ? <span>{total} selected</span> : null;
}

<RiftEvent eventId="evt_summerfest_2026">
<header>
<h2>Pick your tickets</h2>
<SelectionBadge />
</header>
<AvailabilityList />
<ReserveButton requireCaptcha />
</RiftEvent>;