React integration
Section titled “React integration”There are two layers, and most apps use both:
- The web components. Drop
<frak-button-share>,<frak-banner>, and<frak-post-purchase>straight into your JSX. No provider, no hooks. Start here. @frak-labs/react-sdk. Providers and hooks for custom flows: wallet status, modals, referrals, SSO, transactions.
Prerequisites
Section titled “Prerequisites”- A React 18 or 19 project.
- A merchant account on the Frak business dashboard with your domain registered. The main domain is registered at sign-up; add subdomains under Allowed Domains.
If you have not done that yet, start with the Get started guide.
The components in JSX
Section titled “The components in JSX”Install @frak-labs/components, set the config in its own module, and import the components you use before rendering. The package manager guide covers that setup, and Add Frak to a custom website shows a full React example including the TypeScript declaration for the <frak-*> tags.
The React SDK
Section titled “The React SDK”1. Install
Section titled “1. Install”@tanstack/react-query and viem are peer dependencies: the hooks are React Query queries and mutations, and the SDK ships no QueryClient of its own.
npm install @frak-labs/react-sdk @tanstack/react-query viemyarn add @frak-labs/react-sdk @tanstack/react-query viempnpm add @frak-labs/react-sdk @tanstack/react-query viembun add @frak-labs/react-sdk @tanstack/react-query viem2. Set up the providers
Section titled “2. Set up the providers”Three providers, in this order: your QueryClientProvider, then FrakConfigProvider (holds the config), then FrakIFrameClientProvider (creates the listener iframe and the client).
import { import FrakConfigProvider
FrakConfigProvider, import FrakIFrameClientProvider
FrakIFrameClientProvider,} from "@frak-labs/react-sdk";import { class QueryClient
QueryClient, const QueryClientProvider: ({ client, children, }: QueryClientProviderProps) => React.JSX.Element
QueryClientProvider } from "@tanstack/react-query";import type { type PropsWithChildren<P = unknown> = P & { children?: React.ReactNode | undefined;}
PropsWithChildren } from "react";
const const queryClient: QueryClient
queryClient = new new QueryClient(config?: QueryClientConfig): QueryClient
QueryClient();
const const frakConfig: { metadata: { name: string; };}
frakConfig = { metadata: { name: string;}
metadata: { name: string
name: "Your App Name", }, // The domain will be automatically set to window.location.host};
export function function FrakProvider({ children }: PropsWithChildren): React.JSX.Element
FrakProvider({ children: React.ReactNode
children }: type PropsWithChildren<P = unknown> = P & { children?: React.ReactNode | undefined;}
PropsWithChildren) { return ( <const QueryClientProvider: ({ client, children, }: QueryClientProviderProps) => React.JSX.Element
QueryClientProvider client: QueryClient
client={const queryClient: QueryClient
queryClient}> <import FrakConfigProvider
FrakConfigProvider config: { metadata: { name: string; };}
config={const frakConfig: { metadata: { name: string; };}
frakConfig}> <import FrakIFrameClientProvider
FrakIFrameClientProvider>{children: React.ReactNode
children}</import FrakIFrameClientProvider
FrakIFrameClientProvider> </import FrakConfigProvider
FrakConfigProvider> </const QueryClientProvider: ({ client, children, }: QueryClientProviderProps) => React.JSX.Element
QueryClientProvider> );}Wrap your app with it:
import { import FrakProvider
FrakProvider } from './FrakProvider';
function function App(): any
App() { return ( <import FrakProvider
FrakProvider> {/* Your app content */} </import FrakProvider
FrakProvider> );}
export default function App(): any
App;The config takes the same fields as everywhere else. See FrakSetup and Configuration.
3. Read the wallet status
Section titled “3. Read the wallet status”/** * Simple wallet status component */function function WalletStatus(): any
Simple wallet status component
WalletStatus() { const { any
data: const walletStatus: any
walletStatus, const isLoading: any
isLoading, const error: any
error } = any
useWalletStatus();
if (const isLoading: any
isLoading) return <any
div>Loading wallet status...</any
div>; if (const error: any
error) return <any
div>Error: {const error: any
error.any
message}</any
div>;
return ( <any
div> Wallet status:{" "} {const walletStatus: any
walletStatus?.any
key === "connected" ? "Connected" : "Not connected"} </any
div> );}4. Open a modal
Section titled “4. Open a modal”/** * Simple login with frak button */function function LoginButton(): any
Simple login with frak button
LoginButton() { const { any
mutate: const displayModal: any
displayModal, const isPending: any
isPending } = any
useDisplayModal();
const const handleLogin: () => void
handleLogin = () => { const displayModal: any
displayModal({ steps: { login: {};}
steps: { login: {}
login: {}, }, metadata: { i18n: { "sdk.modal.login.description": string; };}
metadata: { i18n: { "sdk.modal.login.description": string;}
i18n: { "sdk.modal.login.description": "Login with Frak to receive up to {{ estimatedReward }}!", }, }, }); };
return ( <any
button onClick: () => void
onClick={const handleLogin: () => void
handleLogin} disabled: any
disabled={const isPending: any
isPending} type: string
type={"button"}> {const isPending: any
isPending ? "Logging in..." : "Login with Frak"} </any
button> );}5. Put it together
Section titled “5. Put it together”function function App(): any
App() { return ( <any
FrakProvider> <any
h1>My Frak Powered app</any
h1> <any
WalletStatus /> <any
LoginButton /> </any
FrakProvider> );}export default function App(): any
App;import { import useDisplayModal
useDisplayModal, import useWalletStatus
useWalletStatus } from "@frak-labs/react-sdk";import { import FrakProvider
FrakProvider } from "./FrakProvider";
function function App(): any
App() { return ( <import FrakProvider
FrakProvider> <any
h1>My Frak Powered app</any
h1> <function WalletStatus(): any
Simple wallet status component
WalletStatus /> <function LoginButton(): any
Simple login with frak button
LoginButton /> </import FrakProvider
FrakProvider> );}export default function App(): any
App;
/** * Simple wallet status component */function function WalletStatus(): any
Simple wallet status component
WalletStatus() { const { any
data: const walletStatus: any
walletStatus, const isLoading: any
isLoading, const error: any
error } = import useWalletStatus
useWalletStatus();
if (const isLoading: any
isLoading) return <any
div>Loading wallet status...</any
div>; if (const error: any
error) return <any
div>Error: {const error: any
error.any
message}</any
div>;
return ( <any
div> Wallet status:{" "} {const walletStatus: any
walletStatus?.any
key === "connected" ? "Connected" : "Not connected"} </any
div> );}
/** * Simple login with frak button */function function LoginButton(): any
Simple login with frak button
LoginButton() { const { any
mutate: const displayModal: any
displayModal, const isPending: any
isPending } = import useDisplayModal
useDisplayModal();
const const handleLogin: () => void
handleLogin = () => { const displayModal: any
displayModal({ steps: { login: {};}
steps: { login: {}
login: {}, }, metadata: { i18n: { "sdk.modal.login.description": string; };}
metadata: { i18n: { "sdk.modal.login.description": string;}
i18n: { "sdk.modal.login.description": "Login with Frak to receive up to {{ estimatedReward }}!", }, }, }); };
return ( <any
button onClick: () => void
onClick={const handleLogin: () => void
handleLogin} disabled: any
disabled={const isPending: any
isPending} type: string
type={"button"}> {const isPending: any
isPending ? "Logging in..." : "Login with Frak"} </any
button> );}import { import FrakConfigProvider
FrakConfigProvider, import FrakIFrameClientProvider
FrakIFrameClientProvider,} from "@frak-labs/react-sdk";import { class QueryClient
QueryClient, const QueryClientProvider: ({ client, children, }: QueryClientProviderProps) => React.JSX.Element
QueryClientProvider } from "@tanstack/react-query";import type { type PropsWithChildren<P = unknown> = P & { children?: React.ReactNode | undefined;}
PropsWithChildren } from "react";
const const queryClient: QueryClient
queryClient = new new QueryClient(config?: QueryClientConfig): QueryClient
QueryClient();
const const frakConfig: { metadata: { name: string; };}
frakConfig = { metadata: { name: string;}
metadata: { name: string
name: "Your App Name", }, // The domain will be automatically set to window.location.host};
export function function FrakProvider({ children }: PropsWithChildren): React.JSX.Element
FrakProvider({ children: React.ReactNode
children }: type PropsWithChildren<P = unknown> = P & { children?: React.ReactNode | undefined;}
PropsWithChildren) { return ( <const QueryClientProvider: ({ client, children, }: QueryClientProviderProps) => React.JSX.Element
QueryClientProvider client: QueryClient
client={const queryClient: QueryClient
queryClient}> <import FrakConfigProvider
FrakConfigProvider config: { metadata: { name: string; };}
config={const frakConfig: { metadata: { name: string; };}
frakConfig}> <import FrakIFrameClientProvider
FrakIFrameClientProvider>{children: React.ReactNode
children}</import FrakIFrameClientProvider
FrakIFrameClientProvider> </import FrakConfigProvider
FrakConfigProvider> </const QueryClientProvider: ({ client, children, }: QueryClientProviderProps) => React.JSX.Element
QueryClientProvider> );}Every hook
Section titled “Every hook”| Hook | Returns | What it does |
|---|---|---|
useWalletStatus() | query | Current wallet status, kept in sync |
useDisplayModal() | mutation | Open a modal from steps, with an optional placement |
useDisplaySharingPage() | mutation | Open the sharing page |
useReferralInteraction() | value | Processes an inbound referral once the client is ready, returns the state or an error |
useSetupReferral() | side effect | Wires referral handling, emits frak:referral-success |
useGetMerchantInformation() | query | Your merchant data as resolved by the backend |
useGetUserReferralStatus() | query | The user’s referral state |
useGetMergeToken() | query | Token used to merge a wallet |
useSiweAuthenticate() | mutation | Sign-In with Ethereum |
useSendTransactionAction() | mutation | Ask the wallet to send a transaction |
useOpenSso() | mutation | Open the SSO flow |
usePrepareSso(params) | query | Prepare an SSO session |
usePrepareSsoUrl(params) | query | Build the SSO URL ahead of time, so the open is a direct user gesture and dodges popup blockers |
useFrakClient() | value | The raw client, or undefined before it is ready |
useFrakConfig() | value | The resolved config. Throws outside a FrakConfigProvider |
Most mutation hooks take an optional { mutations } object of React Query options, and most query hooks take { query }. Two exceptions: useWalletStatus() takes no argument, and usePrepareSso(params) takes the SSO parameters directly.
Full signatures live in the generated SDK reference.
Handle an inbound referral
Section titled “Handle an inbound referral”One hook is enough on the page a referred visitor lands on:
import { useReferralInteraction } from "@frak-labs/react-sdk";
export function ReferralHandler() { const state = useReferralInteraction(); // "idle" | "processing" | a referral state | an Error return null;}Track a purchase
Section titled “Track a purchase”Purchase tracking has no hook: call the action, which needs no client.
import { trackPurchaseStatus } from "@frak-labs/core-sdk/actions";
await trackPurchaseStatus({ customerId: "cust_123", orderId: "order_456", token: "a-unique-order-token",});Rewards are only released once your backend confirms the order with a signed webhook. See Validate purchases from your backend.