Skip to main content

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 Challenge payload 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-js and @stripe/react-stripe-js. See the "Cannot find module" entry above.
  • ticketingConfig.stripe is null. Open DevTools → Network → check the GET /api/v1/events/<eventId>/ticketing/config response. If enabled is true and stripe is null, 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.publishableKey is empty. Same response — check the key value. A missing key fails Stripe's load silently.
  • ticketingConfig.stripe.connectedAccountId is 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 from js.stripe.com/v3. If your script-src Content Security Policy doesn't include js.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.
  • apiBaseUrl pointing 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.