Skip to main content

React to Solid Migration

This guide covers migrating from custom React hook implementations to SolidJS-based web components. This migration is optional but recommended for simpler integration and smaller bundle size.

Why Migrate?

AspectReact HooksSolid Widgets
Bundle SizeLarger (React dependency)Smaller (standalone)
CustomizationFull controlTheme + slots
Development TimeMore code requiredDrop-in solution
MaintenanceCustom UI maintenanceAutomatic updates
ConsistencyVaries by implementationConsistent UX

When to Migrate

Consider migrating when:
  • You want to reduce custom code maintenance
  • Your custom UI closely matches the default widgets
  • You want automatic updates to widget behavior
  • Bundle size is a concern
Keep using React hooks when:
  • You need highly custom UI designs
  • You have complex business logic in the UI layer
  • You want complete control over rendering

Installation

# Add Solid SDK alongside React SDK
npm install @waitify-io/fanfare-sdk-solid

Basic Migration

React Hook Implementation

import { useQueue, useFanfareAuth } from "@waitify-io/fanfare-sdk-react";

function CustomQueueUI() {
  const { isAuthenticated, guest } = useFanfareAuth();
  const { queue, status, position, enter, leave, isLoading } = useQueue("queue_123");

  const handleEnter = async () => {
    if (!isAuthenticated) await guest();
    await enter();
  };

  if (isLoading) return <div className="loading">Loading...</div>;

  if (status === "admitted") {
    return (
      <div className="admitted">
        <h2>You are In!</h2>
        <a href="/checkout">Continue</a>
      </div>
    );
  }

  return (
    <div className="queue-ui">
      {status === "queued" ? (
        <>
          <p>Position: {position}</p>
          <button onClick={leave}>Leave Queue</button>
        </>
      ) : (
        <button onClick={handleEnter}>Enter Queue</button>
      )}
    </div>
  );
}

Solid Widget Implementation

// Setup: Register once at app startup
import { registerWebComponents } from "@waitify-io/fanfare-sdk-solid";

registerWebComponents({
  organizationId: "org_xxx",
  publishableKey: "pk_live_xxx",
});

// Component: Just use the web component
function QueuePage() {
  const widgetRef = useRef<HTMLElement>(null);

  useEffect(() => {
    const widget = widgetRef.current;
    if (!widget) return;

    const handleAdmitted = (e: CustomEvent<{ token: string }>) => {
      window.location.href = `/checkout?token=${e.detail.token}`;
    };

    widget.addEventListener("fanfare-queue-admitted", handleAdmitted);
    return () => widget.removeEventListener("fanfare-queue-admitted", handleAdmitted);
  }, []);

  return <fanfare-queue-widget ref={widgetRef} queue-id="queue_123" />;
}

Event Handling Migration

React Hooks (Callback-based)

function QueueWithCallbacks() {
  const { status, position, admittanceToken } = useQueue("queue_123");

  useEffect(() => {
    if (status === "admitted" && admittanceToken) {
      analytics.track("queue_admitted");
      window.location.href = `/checkout?token=${admittanceToken}`;
    }
  }, [status, admittanceToken]);

  useEffect(() => {
    if (position !== null) {
      analytics.track("queue_position_change", { position });
    }
  }, [position]);

  return <QueueUI />;
}

Solid Widget (Event-based)

function QueueWithEvents() {
  const widgetRef = useRef<HTMLElement>(null);

  useEffect(() => {
    const widget = widgetRef.current;
    if (!widget) return;

    const handleEnter = (e: CustomEvent) => {
      analytics.track("queue_entered", { queueId: e.detail.queueId });
    };

    const handlePositionChange = (e: CustomEvent) => {
      analytics.track("queue_position_change", { position: e.detail.position });
    };

    const handleAdmitted = (e: CustomEvent<{ token: string }>) => {
      analytics.track("queue_admitted");
      window.location.href = `/checkout?token=${e.detail.token}`;
    };

    widget.addEventListener("fanfare-queue-enter", handleEnter);
    widget.addEventListener("fanfare-queue-position-change", handlePositionChange);
    widget.addEventListener("fanfare-queue-admitted", handleAdmitted);

    return () => {
      widget.removeEventListener("fanfare-queue-enter", handleEnter);
      widget.removeEventListener("fanfare-queue-position-change", handlePositionChange);
      widget.removeEventListener("fanfare-queue-admitted", handleAdmitted);
    };
  }, []);

  return <fanfare-queue-widget ref={widgetRef} queue-id="queue_123" />;
}

Styling Migration

React (Custom CSS)

function StyledQueue() {
  return (
    <div className="my-queue-wrapper">
      <div className="queue-header">
        <h2>Join Our Queue</h2>
      </div>
      <div className="queue-content">{/* Custom UI */}</div>
      <button className="queue-button">Enter</button>
    </div>
  );
}

Solid Widget (CSS Variables)

function StyledWidget() {
  return (
    <>
      <style>
        {`
          fanfare-queue-widget {
            --fanfare-primary: #8b5cf6;
            --fanfare-primary-hover: #7c3aed;
            --fanfare-radius: 1rem;
            max-width: 400px;
          }
        `}
      </style>
      <fanfare-queue-widget queue-id="queue_123" />
    </>
  );
}

Partial Migration (Hybrid Approach)

Keep using hooks for some flows while adopting widgets for others:
import { useExperienceJourney } from "@waitify-io/fanfare-sdk-react";
import { registerWebComponents } from "@waitify-io/fanfare-sdk-solid";

// Register widgets
registerWebComponents({
  organizationId: "org_xxx",
  publishableKey: "pk_live_xxx",
});

function HybridPage() {
  // Use hook for complex journey logic
  const { status, start } = useExperienceJourney("exp_123");

  if (status === "idle") {
    // Custom start UI with hooks
    return <CustomStartButton onStart={start} />;
  }

  if (status === "ready") {
    // Use widget for the queue UI
    return <fanfare-queue-widget queue-id="queue_123" />;
  }

  return <LoadingState />;
}

TypeScript Migration

Add Type Declarations

// global.d.ts
declare namespace JSX {
  interface IntrinsicElements {
    "fanfare-queue-widget": React.DetailedHTMLProps<
      React.HTMLAttributes<HTMLElement> & {
        "queue-id": string;
        "show-header"?: string;
        "show-actions"?: string;
        ref?: React.Ref<HTMLElement>;
      },
      HTMLElement
    >;
    "fanfare-draw-widget": React.DetailedHTMLProps<
      React.HTMLAttributes<HTMLElement> & {
        "draw-id": string;
        "show-header"?: string;
        "show-countdown"?: string;
        ref?: React.Ref<HTMLElement>;
      },
      HTMLElement
    >;
    // Add other widgets as needed
  }
}

Event Type Definitions

// types/fanfare-events.ts
interface QueueEnterEventDetail {
  queueId: string;
}

interface QueueAdmittedEventDetail {
  queueId: string;
  token: string;
}

interface QueuePositionChangeEventDetail {
  queueId: string;
  position: number;
}

// Usage
const handleAdmitted = (e: CustomEvent<QueueAdmittedEventDetail>) => {
  window.location.href = `/checkout?token=${e.detail.token}`;
};

Migration Checklist

  • Install @waitify-io/fanfare-sdk-solid
  • Add registerWebComponents call at app startup
  • Add TypeScript declarations for web components
  • Replace custom UIs with widget components
  • Migrate callback logic to event listeners
  • Update styling to use CSS variables
  • Test all distribution flows
  • Remove unused hook imports if fully migrated

Rollback Strategy

If issues arise, you can easily revert:
  1. Keep both implementations during migration
  2. Use feature flags to toggle between them
  3. Gradual rollout with monitoring
function QueuePage() {
  const useWidgets = useFeatureFlag("use-solid-widgets");

  if (useWidgets) {
    return <fanfare-queue-widget queue-id="queue_123" />;
  }

  return <CustomQueueUI queueId="queue_123" />;
}