Troubleshooting
Common pitfalls when integrating @feelrift/react and what to do
about each.
Nothing renders — <AvailabilityList> is empty
The most likely cause: <AvailabilityList> is mounted outside a
<RiftEvent> scope. <AvailabilityList> reads ticket-type data
through useAvailability(), which throws unless an event ID is
provided via the event scope.
Fix: wrap the affected subtree with <RiftEvent eventId="evt_…">
inside your <RiftProvider>:
<RiftProvider>
<RiftEvent eventId="evt_summerfest_2026">
<AvailabilityList />
</RiftEvent>
</RiftProvider>
See Getting started → your first checkout for the full provider chain.
RiftApiError { code: "captcha_unavailable" } thrown by <ReserveButton> / <CheckoutForm>
<ReserveButton requireCaptcha /> and <CheckoutForm> require a
captcha solution to be present before they fire their mutating
request. If no <CaptchaWidget> is mounted in the subtree, the
SDK throws this error before the request leaves the client.
Fix: mount <CaptchaWidget> somewhere inside the same
<RiftEvent> scope:
<RiftEvent eventId="evt_summerfest_2026">
<AvailabilityList />
<CaptchaWidget />
<ReserveButton requireCaptcha />
</RiftEvent>
The widget can be visible or invisible (autoVerify +
asChild + an aria-hidden slot). Either way, it must be
mounted somewhere in the subtree so a solution is available
when the mutating call fires.
Cannot find module '@stripe/react-stripe-js'
<CheckoutForm> for paid events requires Stripe peers — they are
NOT bundled with the SDK.
Fix:
pnpm add @stripe/stripe-js @stripe/react-stripe-js
Free events don't need these peers — <CheckoutForm> detects the
ticketingConfig.enabled === true && ticketingConfig.stripe === null
branch and renders the free path without loading any Stripe code.
"You're importing a component that needs <...>" in Next.js App Router
Every SDK hook and component is a client component. Importing one
into a server component (a file without "use client") fails the
build with a clear error.
Fix: either mark the importing file with "use client" at the
top, or move the SDK usage to a child client component and pass
data down from the server component as props.
"use client";
import { RiftProvider } from "@feelrift/react";
// …
For server-side data fetching (route handlers, server actions),
import from @feelrift/react/client instead — that entry has no
React dependency.
Hydration warning in Next.js App Router
The SDK gates persistent state behind useHasHydrated() so the
first server render matches the first client render. If you read
persisted state directly in a custom component, gate the same
way:
import { useHasHydrated, useRiftStore } from "@feelrift/react";
function MyBadge() {
const hasHydrated = useHasHydrated();
const reservation = useRiftStore((s) => s.reservation);
if (!hasHydrated) return null;
return reservation ? <span>{reservation.data.id}</span> : null;
}
See Server-side rendering for the full rules.
useSearchParams() should be wrapped in a Suspense boundary
In Next.js 15 App Router, any client component calling
useSearchParams() must be rendered inside a <Suspense>
boundary. The order-status return-page pattern uses this hook —
wrap it appropriately:
import { Suspense } from "react";
import { OrderReturnPage } from "./order-return-client";
export default function Page({ params }: { params: { orderId: string } }) {
return (
<Suspense fallback={<p>Loading…</p>}>
<OrderReturnPage orderId={params.orderId} />
</Suspense>
);
}
See Securing the order-status token.
Captcha widget renders but nothing happens on click
Most common cause: the configured altchaBaseUrl doesn't resolve.
By default the SDK reaches https://altcha.feelrift.com, which
works for production. If you set altchaBaseUrl to a staging
host or a localhost URL, verify:
- The URL is reachable from the browser (CORS allows your origin).
- The endpoint serves a JSON
Challengepayload at/altcha/challenge. - Network errors in DevTools' console / network panel point at the right host.
Open DevTools' Network panel and click Verify — you should see a
GET /altcha/challenge request. Inspect the response to confirm
it's well-formed.
Stripe Elements doesn't render
The <PaymentElement> inside <CheckoutForm> mounts only on the
paid branch — when the event's Ticketing config has a non-null
stripe. If Elements never renders for a paid event, the likely
causes:
- Stripe peers missing. Install
@stripe/stripe-jsand@stripe/react-stripe-js. See the "Cannot find module" entry above. ticketingConfig.stripeis null. Open DevTools → Network → check theGET /api/v1/events/<eventId>/ticketing/configresponse. Ifenabledistrueandstripeisnull, every public ticket on the event is free — it's a free-event flow, not a paid one, and the SDK never mounts Elements.ticketingConfig.stripe.publishableKeyis empty. Same response — check the key value. A missing key fails Stripe's load silently.ticketingConfig.stripe.connectedAccountIdis missing or wrong. Use the value returned by the Ticketing config response without replacing or caching it across events. Stripe.js must be initialized with that account context.- CSP blocks
js.stripe.com. The SDK lazy-loads Stripe.js fromjs.stripe.com/v3. If yourscript-srcContent Security Policy doesn't includejs.stripe.com, the load fails. Add it alongside any nonce / hash you're using.
RiftApiError { code: "unknown" } for every request
code: "unknown" with status: 0 means a transport-layer
failure: the request never reached the server. Likely causes:
- CORS rejection — browser blocks the response before it reaches the SDK.
apiBaseUrlpointing at the wrong origin.- Network offline or DNS failure.
Open DevTools' Network panel. If the request shows up as
(failed) or (cors), the URL is reachable but the response is
being blocked. If the request doesn't appear at all, the
apiBaseUrl is wrong or fetch is being intercepted by
something earlier in the stack.
Related
- Error handling — the narrowing pattern
for every
RiftApiErrorcode. - Installation — framework-specific provider placement.
- Server-side rendering — hydration guarantees and consumer responsibilities.