Skip to main content
developers/donations-sdk/integration-examples/react-app
Developer5 min read

React app integration

Add the Donations Embed to any React 18 / 19 application using the typed wrapper component.

React app integration

This guide adds the Donations Embed to a React 18 or 19 application using the typed wrapper component, <FlockDonate>. It works with Vite, Create React App, Remix client routes, Astro's React island, and any other React tooling that builds for the browser.

For Next.js (App Router or Pages Router), see the dedicated Next.js guide — it covers the SSR-vs-CSR considerations specific to that framework.

Step 1: Generate a sandbox publishable key

In the 1st Flock Hub:

  1. Open Donations Embed → Keys.
  2. Click Create key pair.
  3. Set environment to Sandbox and name it (e.g. My React app - sandbox).
  4. Copy the publishable key (starts with pk_test_).

Step 2: Install the wrapper

npm install @1st-flock/donations-embed @1st-flock/donations-embed-react
# or
pnpm add @1st-flock/donations-embed @1st-flock/donations-embed-react
# or
yarn add @1st-flock/donations-embed @1st-flock/donations-embed-react

The wrapper's peer dependencies are React 18 or 19 and @1st-flock/donations-embed.

Step 3: Drop in the component

import { FlockDonate } from "@1st-flock/donations-embed-react";

// Replace pk_test_REPLACE_ME with your publishable key.
const PUBLISHABLE_KEY = "pk_test_REPLACE_ME";

export function GivePage() {
  return (
    <main className="give-page">
      <h1>Give to Example Church</h1>
      <p>Thank you for partnering with us. Every gift goes directly toward our mission.</p>

      <FlockDonate
        publishableKey={PUBLISHABLE_KEY}
        mode="inline"
      />
    </main>
  );
}

Importing FlockDonate automatically registers the underlying <flock-donate> web component on window.customElements. There is no separate setup step.

In production, read the publishable key from your environment variables instead of hard-coding it (it is safe to expose to the browser, but checking it into source control is bad hygiene):

const PUBLISHABLE_KEY = import.meta.env.VITE_FLOCK_PUBLISHABLE_KEY!;
// or for Create React App:
const PUBLISHABLE_KEY = process.env.REACT_APP_FLOCK_PUBLISHABLE_KEY!;

Step 4: Test the donation flow (sandbox)

  1. Run your dev server: npm run dev (or npm start).
  2. Navigate to the page where you placed <FlockDonate>.
  3. Walk through the donor flow.
  4. At the payment step, use these sandbox card details:

See Getting Started → Sandbox testing for the canonical sandbox card details (4111 1111 1111 1111, exp 10/29, CVV 123).

  1. Submit. You should see a confirmation screen with an ID like don_8a1b9c4d.
  2. Open the Hub's Donations tab — your test donation appears within a few seconds, marked with a TEST badge.

Step 5: Switch to live

When you are ready to take real donations:

  1. Generate a live key pair in the Hub (environment set to Live).
  2. Update the environment variable holding the publishable key.
  3. Re-deploy.

Customisation

React props

Every web-component attribute is exposed as a React prop. Comma-list attributes accept arrays or strings.

<FlockDonate
  publishableKey={PUBLISHABLE_KEY}
  mode="modal"
  triggerLabel="Give"
  funds={["general", "building", "missions"]}
  defaultFund="general"
  coverFees
  theme="modern"
  successMode="callback"
  suggestedAmounts={[25, 50, 100, 500]}
  minAmount={5}
  maxAmount={5000}
  language="en"
/>

React event handlers

The wrapper translates the five CustomEvents into typed on* props:

import { FlockDonate } from "@1st-flock/donations-embed-react";

export function GivePageWithAnalytics() {
  return (
    <FlockDonate
      publishableKey={PUBLISHABLE_KEY}
      mode="inline"
      onOpened={(detail) => {
        analytics.track("donation_started", { source: detail.source });
      }}
      onStep={(detail) => {
        analytics.track("donation_step", { step: detail.step, total: detail.total });
      }}
      onComplete={(detail) => {
        analytics.track("donation_completed", {
          confirmationId: detail.confirmationId,
          amount: detail.amount,
          fund: detail.fund.name,
          frequency: detail.frequency,
        });
      }}
      onFailed={(detail) => {
        analytics.track("donation_failed", { code: detail.code });
        console.error("Donation failed:", detail.userMessage);
      }}
      onClosed={(detail) => {
        analytics.track("donation_closed", { stepReached: detail.stepReached });
      }}
    />
  );
}

Imperative open / close

For modal and drawer modes, the wrapper forwards ref to the underlying <flock-donate> HTMLElement so you can imperatively open or close the flow:

import { useRef } from "react";
import { FlockDonate, type FlockDonateElement } from "@1st-flock/donations-embed-react";

export function CustomLauncher() {
  const ref = useRef<FlockDonateElement>(null);

  return (
    <>
      <button onClick={() => ref.current?.open()}>Open custom launcher</button>

      <FlockDonate
        ref={ref}
        publishableKey={PUBLISHABLE_KEY}
        mode="modal"
        // The default trigger button is hidden because we are launching imperatively.
        triggerLabel=""
      />
    </>
  );
}

Per-fund pages

A common pattern: a "Give" landing page that links to per-fund pages with the embed pre-configured for each fund.

function FundDonationPage({ fundId, fundName }: { fundId: string; fundName: string }) {
  return (
    <main>
      <h1>Give to {fundName}</h1>
      <FlockDonate
        publishableKey={PUBLISHABLE_KEY}
        funds={[fundId]}
        defaultFund={fundId}
      />
    </main>
  );
}

The full attribute reference is in Browser Embed.

Common pitfalls

Note: TypeScript complains that FlockDonate is not a valid JSX element. Make sure you imported it from @1st-flock/donations-embed-react, not from @1st-flock/donations-embed. The latter exports the underlying web component module, not a React component.

Note: The embed does not render and I see "publishable-key required" in the console. You either passed an empty string, or you are reading from an environment variable that is not set. Vite environment variables must be prefixed VITE_ to be exposed to the client; CRA variables must be prefixed REACT_APP_. Confirm the variable is exposed before debugging anything else.

Note: My UI library's button styles override the launcher button. The launcher renders inside Shadow DOM and is isolated from your CSS. If your launcher button looks unstyled, you are looking at a different button — <FlockDonate mode="modal"> renders the launcher itself. To wrap it in your own button, use the imperative ref.current.open() pattern shown above and put your custom button outside the embed.

Note: onComplete fires twice in development. That is React 18+ Strict Mode replaying effects. The web component itself only fires the event once; React's Strict Mode mounts twice in dev. The duplicated call goes away in production builds.

Note: The component re-mounts every time I change a prop. That is normal. The web component handles attribute changes internally without remounting; if you see DOM remounting, you have a key prop bound to the changing value, or the parent component is unmounting and remounting on its own (often caused by inline arrow functions in useEffect deps).

Next steps

  • For Next.js specifically, see the dedicated Next.js guide — it covers SSR considerations.
  • Read the full Browser Embed reference for every attribute and event.
  • Receive donation events on your server — see Webhooks.
Developer Portal