React Integration

Web Components work seamlessly in React. This guide covers best practices and TypeScript integration.


Basic Usage

Import the SDK once at your app's entry point:

// src/main.tsx or src/index.tsx
// Set the config BEFORE importing the SDK: elements read it when they are
// constructed, so a config assigned later (e.g. in a useEffect) can be missed.
window.QariybConfig = { publishableKey: 'pk_live_...' };

import '@qariyb/js';
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

ReactDOM.createRoot(document.getElementById('root')!).render(<App />);

Then use components in JSX:

// src/components/TicketSection.tsx
export function TicketSection({ eventId }: { eventId: string }) {
  return (
    <section>
      <h2>Get Your Tickets</h2>
      <qariyb-checkout event-id={eventId}></qariyb-checkout>
    </section>
  );
}

TypeScript Declarations

Add type declarations for the web components:

// src/types/qariyb.d.ts
import type { ThemePreset } from '@qariyb/js';

declare namespace JSX {
  interface IntrinsicElements {
    'qariyb-checkout': React.DetailedHTMLProps<
      React.HTMLAttributes<HTMLElement> & {
        'event-id': string;
        theme?: ThemePreset;
        'accent-color'?: string;
        'prefill-email'?: string;
        'prefill-name'?: string;
        'on-success'?: string;
        'on-error'?: string;
      },
      HTMLElement
    >;
    'qariyb-button': React.DetailedHTMLProps<
      React.HTMLAttributes<HTMLElement> & {
        'event-id': string;
        theme?: ThemePreset;
        'accent-color'?: string;
        size?: 'sm' | 'md' | 'lg';
      },
      HTMLElement
    >;
    'qariyb-tickets': React.DetailedHTMLProps<
      React.HTMLAttributes<HTMLElement> & {
        'event-id': string;
        theme?: ThemePreset;
        'accent-color'?: string;
      },
      HTMLElement
    >;
    'qariyb-event-card': React.DetailedHTMLProps<
      React.HTMLAttributes<HTMLElement> & {
        'event-id': string;
        theme?: ThemePreset;
        'accent-color'?: string;
        'show-image'?: string;
        'show-price'?: string;
        'show-features'?: string;
      },
      HTMLElement
    >;
    'qariyb-event-list': React.DetailedHTMLProps<
      React.HTMLAttributes<HTMLElement> & {
        'organization-id': string;
        theme?: ThemePreset;
        'accent-color'?: string;
        limit?: string;
        layout?: 'grid' | 'list';
        'show-past'?: string;
      },
      HTMLElement
    >;
  }
}

Event Handling with Refs

Use refs to attach event listeners:

import { useRef, useEffect } from 'react';
import type { CheckoutSuccessDetail, QariybCustomEvent } from '@qariyb/js';

interface Props {
  eventId: string;
  onSuccess?: (order: CheckoutSuccessDetail) => void;
}

export function Checkout({ eventId, onSuccess }: Props) {
  const checkoutRef = useRef<HTMLElement>(null);

  useEffect(() => {
    const element = checkoutRef.current;
    if (!element) return;

    const handleSuccess = (e: Event) => {
      const customEvent = e as QariybCustomEvent<CheckoutSuccessDetail>;
      onSuccess?.(customEvent.detail.data);
    };

    element.addEventListener('qariyb:checkout:success', handleSuccess);

    return () => {
      element.removeEventListener('qariyb:checkout:success', handleSuccess);
    };
  }, [onSuccess]);

  return (
    <qariyb-checkout
      ref={checkoutRef}
      event-id={eventId}
    ></qariyb-checkout>
  );
}

Global Event Listener

For app-wide event handling, use a custom hook:

// src/hooks/useQariybEvents.ts
import { useEffect } from 'react';
import type { QariybCustomEvent, CheckoutSuccessDetail } from '@qariyb/js';

export function useQariybSuccessEvent(
  callback: (data: CheckoutSuccessDetail) => void
) {
  useEffect(() => {
    const handler = (e: Event) => {
      const customEvent = e as QariybCustomEvent<CheckoutSuccessDetail>;
      callback(customEvent.detail.data);
    };

    window.addEventListener('qariyb:checkout:success', handler);
    return () => window.removeEventListener('qariyb:checkout:success', handler);
  }, [callback]);
}

Usage:

import { useQariybSuccessEvent } from './hooks/useQariybEvents';

function App() {
  useQariybSuccessEvent((data) => {
    console.log('Order completed:', data.order.id);
    // Update app state, redirect, etc.
  });

  return <qariyb-checkout event-id="01HW..."></qariyb-checkout>;
}

Configuration with Context

Manage SDK configuration at the app level:

// src/context/QariybContext.tsx
import { createContext, useContext, useEffect, ReactNode } from 'react';
import type { QariybConfig } from '@qariyb/js';

const QariybContext = createContext<QariybConfig | null>(null);

interface Props {
  config: QariybConfig;
  children: ReactNode;
}

export function QariybProvider({ config, children }: Props) {
  useEffect(() => {
    window.QariybConfig = config;
  }, [config]);

  return (
    <QariybContext.Provider value={config}>
      {children}
    </QariybContext.Provider>
  );
}

export function useQariybConfig() {
  return useContext(QariybContext);
}

Usage:

// src/App.tsx
import { QariybProvider } from './context/QariybContext';

function App() {
  return (
    <QariybProvider
      config={{
        publishableKey: 'pk_live_...',
        theme: 'dark',
        variables: {
          colorPrimary: '#8b5cf6',
        },
      }}
    >
      <TicketSection eventId="01HW..." />
    </QariybProvider>
  );
}

Wrapper Component Pattern

Create reusable wrapper components with typed callbacks:

// src/components/QariybCheckout.tsx
import { useRef, useEffect, forwardRef, useImperativeHandle } from 'react';
import type {
  ThemePreset,
  CheckoutSuccessDetail,
  CheckoutErrorDetail,
} from '@qariyb/js';

interface Props {
  eventId: string;
  theme?: ThemePreset;
  accentColor?: string;
  prefillEmail?: string;
  prefillName?: string;
  onSuccess?: (data: CheckoutSuccessDetail) => void;
  onError?: (data: CheckoutErrorDetail) => void;
  onStepChanged?: (from: string, to: string) => void;
}

export const QariybCheckout = forwardRef<HTMLElement, Props>(function QariybCheckout(
  { eventId, theme, accentColor, prefillEmail, prefillName, onSuccess, onError, onStepChanged },
  ref
) {
  const elementRef = useRef<HTMLElement>(null);

  useImperativeHandle(ref, () => elementRef.current!);

  useEffect(() => {
    const element = elementRef.current;
    if (!element) return;

    const handlers: Array<[string, EventListener]> = [];

    if (onSuccess) {
      const handler = (e: Event) => {
        onSuccess((e as CustomEvent).detail.data);
      };
      handlers.push(['qariyb:checkout:success', handler]);
      element.addEventListener('qariyb:checkout:success', handler);
    }

    if (onError) {
      const handler = (e: Event) => {
        onError((e as CustomEvent).detail.data);
      };
      handlers.push(['qariyb:checkout:error', handler]);
      element.addEventListener('qariyb:checkout:error', handler);
    }

    if (onStepChanged) {
      const handler = (e: Event) => {
        const { from, to } = (e as CustomEvent).detail.data;
        onStepChanged(from, to);
      };
      handlers.push(['qariyb:checkout:step-changed', handler]);
      element.addEventListener('qariyb:checkout:step-changed', handler);
    }

    return () => {
      handlers.forEach(([event, handler]) => {
        element.removeEventListener(event, handler);
      });
    };
  }, [onSuccess, onError, onStepChanged]);

  return (
    <qariyb-checkout
      ref={elementRef}
      event-id={eventId}
      theme={theme}
      accent-color={accentColor}
      prefill-email={prefillEmail}
      prefill-name={prefillName}
    ></qariyb-checkout>
  );
});

Usage:

<QariybCheckout
  eventId="01HW..."
  theme="light"
  onSuccess={(data) => {
    console.log('Order:', data.order.id);
    navigate('/confirmation');
  }}
  onError={(data) => {
    toast.error(data.error);
  }}
/>

Server-Side Rendering (Next.js)

For SSR frameworks, load the SDK only on the client:

Client Component

// src/components/QariybCheckoutClient.tsx
'use client';

import { useEffect, useState } from 'react';

interface Props {
  eventId: string;
}

export function QariybCheckoutClient({ eventId }: Props) {
  const [isLoaded, setIsLoaded] = useState(false);

  useEffect(() => {
    import('@qariyb/js').then(() => {
      setIsLoaded(true);
    });
  }, []);

  if (!isLoaded) {
    return <div>Loading checkout...</div>;
  }

  return <qariyb-checkout event-id={eventId}></qariyb-checkout>;
}

Dynamic Import

// src/app/tickets/page.tsx
import dynamic from 'next/dynamic';

const QariybCheckout = dynamic(
  () => import('@/components/QariybCheckoutClient').then(mod => mod.QariybCheckoutClient),
  { ssr: false, loading: () => <div>Loading...</div> }
);

export default function TicketsPage() {
  return <QariybCheckout eventId="01HW..." />;
}

App Router Layout

// src/app/layout.tsx
'use client';

import { useEffect } from 'react';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  useEffect(() => {
    // Configure before SDK loads
    window.QariybConfig = {
      publishableKey: 'pk_live_...',
      theme: 'light',
      variables: {
        colorPrimary: '#4f46e5',
      },
    };

    // Load SDK
    import('@qariyb/js');
  }, []);

  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

Vue Integration

For Vue, the approach is similar:

<template>
  <qariyb-checkout :event-id="eventId"></qariyb-checkout>
</template>

<script setup>
import { onMounted } from 'vue';

const props = defineProps(['eventId']);

onMounted(() => {
  import('@qariyb/js');
});
</script>

Common Pitfalls

Self-closing tags don't work

// Won't work
<qariyb-checkout event-id="01HW..." />

// Works
<qariyb-checkout event-id="01HW..."></qariyb-checkout>

Attributes are strings

// Doesn't work - boolean
<qariyb-event-card show-image={false}></qariyb-event-card>

// Works - string
<qariyb-event-card show-image="false"></qariyb-event-card>

TypeScript errors

If TypeScript complains about unknown elements, ensure your qariyb.d.ts file is included in your tsconfig.json.

Was this page helpful?