← Back to SPA Demo

SPA Implementation Guide

This guide explains how to integrate the Classy Embedded SDK into Single Page Applications built with React, Vue, Angular, or other SPA frameworks.

Key Concept: In SPAs, pages don't reload when navigating. The SDK provides lifecycle APIs to initialize and destroy the SDK as users navigate between routes.

Quick Start

1. Disable Automatic Initialization

Add the ?manual-init=true query parameter to the SDK script URL:

<!-- In your index.html --> <script async src="https://embed.classy.org/api/sdk/js/YOUR_ORG_ID?manual-init=true"></script>

2. Initialize on Route Enter

Call eg.init() when the donation page mounts. The SDK discovers campaigns from DOM elements with data-classy-campaign attributes:

// SDK discovers campaigns from DOM - no campaignId needed in init() await window.eg.init(); // Then open modal for any campaign on the page window.eg.modal.open('YOUR_CAMPAIGN_ID');

3. Destroy on Route Leave

Call eg.destroy() when leaving the donation page:

window.eg.destroy();

API Reference

All methods are available on window.eg:

Method Returns Description
eg.init(config?) Promise<void> Initialize the SDK. Returns when initialization is complete.
eg.destroy() void Clean up the SDK, remove event listeners, restore DOM elements.
eg.isInitialized() boolean Check if the SDK is currently initialized.
eg.modal ModalSDK Modal API for opening/closing donation modal (available after init).
eg.sendAnalyticsEvent(name, data) void Send custom analytics events (available after init).

Framework Examples

React with React Router

import { useEffect } from 'react'; // campaignId is used for the DOM element, not for eg.init() function DonatePage({ campaignId }) { useEffect(() => { let mounted = true; const initSDK = async () => { if (window.eg?.init && mounted) { // SDK discovers campaigns from DOM elements await window.eg.init(); } }; initSDK(); // Cleanup on unmount return () => { mounted = false; window.eg?.destroy(); }; }, []); // No campaignId dependency - SDK handles it return ( <div> <h1>Make a Donation</h1> {/* SDK discovers this element by data-classy-campaign attribute */} <div id="donate-grid" data-classy-campaign={campaignId}></div> </div> ); }

Vue 3 Composition API

<script setup> import { onMounted, onUnmounted } from 'vue'; // campaignId is used for the DOM element, not for eg.init() const props = defineProps(['campaignId']); onMounted(async () => { if (window.eg?.init) { // SDK discovers campaigns from DOM elements await window.eg.init(); } }); onUnmounted(() => { window.eg?.destroy(); }); </script> <template> <div> <h1>Make a Donation</h1> <!-- SDK discovers this element by data-classy-campaign attribute --> <div id="donate-grid" :data-classy-campaign="campaignId"></div> </div> </template>

Angular Component

import { Component, Input, OnInit, OnDestroy } from '@angular/core'; declare global { interface Window { eg: any; } } @Component({ selector: 'app-donate', // SDK discovers element by data-classy-campaign attribute template: ` <div> <h1>Make a Donation</h1> <div id="donate-grid" [attr.data-classy-campaign]="campaignId"></div> </div> ` }) export class DonateComponent implements OnInit, OnDestroy { // campaignId is used for the DOM element, not for eg.init() @Input() campaignId: string = ''; async ngOnInit() { if (window.eg?.init) { // SDK discovers campaigns from DOM elements await window.eg.init(); } } ngOnDestroy() { window.eg?.destroy(); } }

Next.js (App Router)

'use client'; import { useEffect } from 'react'; // campaignId is used for the DOM element, not for eg.init() export default function DonatePage({ campaignId }) { useEffect(() => { let mounted = true; const initSDK = async () => { // Wait for SDK to load const waitForSDK = () => new Promise((resolve) => { if (window.eg?.init) return resolve(window.eg); const interval = setInterval(() => { if (window.eg?.init) { clearInterval(interval); resolve(window.eg); } }, 100); }); const eg = await waitForSDK(); if (mounted) { // SDK discovers campaigns from DOM elements await eg.init(); } }; initSDK(); return () => { mounted = false; window.eg?.destroy(); }; }, []); // No campaignId dependency - SDK handles it return ( <div> <h1>Make a Donation</h1> {/* SDK discovers this element by data-classy-campaign attribute */} <div id="donate-grid" data-classy-campaign={campaignId}></div> </div> ); }
Note: Next.js requires the 'use client' directive since the SDK interacts with the DOM and window object.
Important: The element's id attribute must match a component ID from your campaign configuration. You can find this ID in the Classy Studio campaign settings.

Best Practices

✓ Do:
✗ Don't:

Troubleshooting

Donation grid doesn't appear after navigation

Make sure you're calling eg.destroy() when leaving the page. The SDK restores the original DOM element on destroy, which allows it to be reinitialized on the next visit.

Modal doesn't open when clicking Donate

Ensure the SDK is fully initialized before the user can interact with the donation form. Use the Promise returned by init() to know when it's ready.

SDK not found (window.eg is undefined)

The SDK script loads asynchronously. Wait for it to load before calling init(). See the Next.js example for a pattern to wait for the SDK.