Skip to main content

React integration: component patterns

This page covers the four component integration patterns for React. If you haven't set up the SDK yet, start with Getting started.

Component mounting patterns

Basic mounting with useEffect

All Weavr SDK components follow this pattern in React:

import React, { useEffect, useRef } from "react";

const WeavrComponent: React.FC = () => {
const containerIdRef = useRef(`weavr-${Date.now()}`);
const sdkComponentRef = useRef<any>(null);

useEffect(() => {
// Create and mount SDK component
const component = /* create SDK component */;
sdkComponentRef.current = component;
component.mount(document.getElementById(containerIdRef.current));

// Cleanup on unmount
// For forms use: formRef.current?.destroy()
// For spans use: spanRef.current?.unmount()
return () => {
try {
sdkComponentRef.current?.destroy?.() ?? sdkComponentRef.current?.unmount?.();
} catch (e) {
console.warn("Error cleaning up component:", e);
}
};
}, []);

return <div id={containerIdRef.current}></div>;
};

Reusable custom hook pattern

Create a custom hook for cleaner component code:

// src/hooks/useWeavrForm.ts
import { useEffect, useRef, useState, useCallback } from "react";

interface UseWeavrFormResult {
containerId: string;
tokenize: () => Promise<string>;
isReady: boolean;
error: string | null;
}

export function useWeavrForm(
fieldName: string,
fieldType: string,
options?: {
placeholder?: string;
maxlength?: number;
style?: object;
}
): UseWeavrFormResult {
const formRef = useRef<any>(null);
const inputRef = useRef<any>(null);
const [isReady, setIsReady] = useState(false);
const [error, setError] = useState<string | null>(null);
const containerIdRef = useRef(`weavr-${fieldName}-${Date.now()}`);

useEffect(() => {
if (typeof window.OpcUxSecureClient === "undefined") {
setError("Weavr SDK not loaded");
return;
}

try {
const form = window.OpcUxSecureClient.form();
const input = form.input(fieldName, fieldType, options);

formRef.current = form;
inputRef.current = input;
input.mount(document.getElementById(containerIdRef.current));

input.on("ready", () => {
setIsReady(true);
setError(null);
});
} catch (err) {
setError(`Failed to create ${fieldName} input: ${err}`);
}

return () => {
try {
formRef.current?.destroy();
} catch (e) {
console.warn(`Error destroying ${fieldName} form:`, e);
}
};
}, [fieldName, fieldType]);

const tokenize = useCallback((): Promise<string> => {
return new Promise((resolve, reject) => {
if (!formRef.current) {
reject(new Error("Form not initialized"));
return;
}

formRef.current.tokenize(
(tokens: Record<string, string>) => {
resolve(tokens?.[fieldName] || "");
},
(err: any) => {
reject(err);
}
);
});
}, [fieldName]);

return {
containerId: containerIdRef.current,
tokenize,
isReady,
error,
};
}

Usage:

import React from "react";
import { useWeavrForm } from "@/hooks/useWeavrForm";

const PasswordInput: React.FC = () => {
const { containerId, tokenize, isReady } = useWeavrForm(
"password",
"password",
{
placeholder: "Enter password",
}
);

const handleSubmit = async () => {
const token = await tokenize();
console.log("Token:", token);
};

return (
<div>
<div id={containerId} className="secure-input" />
<button onClick={handleSubmit} disabled={!isReady}>
Submit
</button>
</div>
);
};

Reusable span hook pattern

Create a custom hook for displaying sensitive card data (card number, CVVCVV Card Verification Value - the 3-digit security code printed on a payment card, used to authenticate card-not-present transactions. Weavr returns CVV in tokenized form on `GET /managed_cards/{id}` (with a stepped-up token); the value is only detokenized inside the SDK's secure CVV display component., PINPIN Personal Identification Number - the numeric code a cardholder enters to authorize chip-and-PIN purchases and ATM withdrawals. PIN is only present on physical managed cards. Weavr returns it tokenized on `GET /managed_cards/{id}` (with a stepped-up token), and the SDK detokenizes it inside a secure PIN display component.):

// src/hooks/useWeavrSpan.ts
import { useEffect, useRef, useState } from "react";

interface UseWeavrSpanResult {
containerId: string;
isReady: boolean;
error: string | null;
requiresAuth: boolean;
}

export function useWeavrSpan(
field: "cardNumber" | "cvv" | "cardPin",
token: string | null,
options?: { style?: Record<string, string> }
): UseWeavrSpanResult {
const spanRef = useRef<any>(null);
const [isReady, setIsReady] = useState(false);
const [error, setError] = useState<string | null>(null);
const containerIdRef = useRef(`weavr-span-${field}-${Date.now()}`);

useEffect(() => {
if (!token) {
setIsReady(false);
return;
}

if (typeof window.OpcUxSecureClient === "undefined") {
setError("Weavr SDK not loaded");
return;
}

try {
// Span styles are flat - no state nesting (unlike inputs)
const span = window.OpcUxSecureClient.span(field, token, options);
spanRef.current = span;
span.mount(containerIdRef.current);

span.on("ready", () => {
setIsReady(true);
setError(null);
});
} catch (err) {
setError(`Failed to create ${field} display: ${err}`);
}

return () => {
try {
spanRef.current?.unmount();
} catch (e) {
console.warn(`Error unmounting ${field} span:`, e);
}
spanRef.current = null;
setIsReady(false);
};
}, [field, token]);

return {
containerId: containerIdRef.current,
isReady,
error,
requiresAuth: false, // caller handles auth via associate()
};
}
Span styles vs input styles

SpansPAN Primary Account Number - the long card number (typically 16 digits) printed or embossed on a payment card and used to identify the card on the payment network. Weavr never returns the raw PAN to your client; `GET /managed_cards/{id}` returns the PAN in tokenized form as `cardNumber`, and the value is only detokenized inside a Secure UI card-number component (a sandboxed iframe on the web, a secure native view on mobile). use flat style objects: { style: { fontSize: '18px' } }. Inputs use state-nested style objects: { style: { base: { fontSize: '16px' }, invalid: { color: '#dc3545' } } }. Valid states: base, empty, valid, invalid.

Reusable KYC/KYB hook pattern

Create a custom hook for verification flows:

// src/hooks/useWeavrKyc.ts
import { useEffect, useRef, useState, useCallback } from "react";

type KycType = "consumer_kyc" | "kyb" | "directorKyc";
type KycStatus = "pending" | "started" | "completed" | "error";

interface UseWeavrKycResult {
containerId: string;
status: KycStatus;
start: () => void;
reset: () => void;
error: string | null;
requiresAuth: boolean;
}

export function useWeavrKyc(
type: KycType,
options?: { reference?: string; lang?: string }
): UseWeavrKycResult {
const containerIdRef = useRef(`weavr-${type}-${Date.now()}`);
const [status, setStatus] = useState<KycStatus>("pending");
const [error, setError] = useState<string | null>(null);

useEffect(() => {
if (status !== "started") return;

if (typeof window.OpcUxSecureClient === "undefined") {
setError("Weavr SDK not loaded");
setStatus("error");
return;
}

const selector = `#${containerIdRef.current}`;

try {
if (type === "consumer_kyc") {
// consumer_kyc().init() uses a single named-property options object,
// whereas kyb().init() and kyc().init() use positional arguments.
window.OpcUxSecureClient.consumer_kyc().init({
selector,
reference: options?.reference,
lang: options?.lang,
onMessage: (message: string) => {
if (message === "kycSubmitted") setStatus("completed");
},
onError: (err: any) => {
setError("KYC verification failed");
setStatus("error");
},
});
} else if (type === "kyb") {
// kyb().init(selector, { reference }, listener, { lang }) - positional args
window.OpcUxSecureClient.kyb().init(
selector,
{ reference: options?.reference },
(messageType: string) => {
if (messageType === "kybSubmitted") setStatus("completed");
},
{ lang: options?.lang }
);
} else if (type === "directorKyc") {
// kyc().init(selector, { reference }, listener, { lang }) - positional args
// Director KYC does NOT require associate()
window.OpcUxSecureClient.kyc().init(
selector,
{ reference: options?.reference },
(messageType: string) => {
if (messageType === "kycSubmitted") setStatus("completed");
},
{ lang: options?.lang }
);
}
} catch (err) {
setError(`Failed to create ${type} component: ${err}`);
setStatus("error");
}
}, [type, status, options?.reference, options?.lang]);

const start = useCallback(() => {
setStatus("started");
setError(null);
}, []);

const reset = useCallback(() => {
setStatus("pending");
setError(null);
}, []);

return {
containerId: containerIdRef.current,
status,
start,
reset,
error,
requiresAuth: type !== "directorKyc", // directorKyc doesn't need auth
};
}

Component patterns

The Weavr SDK components follow a few distinct patterns. This section demonstrates each pattern with a representative example. For component-specific parameters and options, refer to the individual component documentation.

PatternComponentsKey characteristics
Input + tokenizeTokenize Replace a card's primary account number (PAN) with a unique digital token that stands in for the real card during a transaction. When a cardholder adds a card to Apple Pay or Google Pay via push provisioning, the wallet provider stores a device-specific token rather than the underlying PAN, so the real card number isn't exposed on the device or shared with merchants.Password, Confirm password, Passcode, Confirm passcodeform()input()mount()tokenize()
Authenticated displayCard number, CVV, Show card PINassociate()span()mount()
Authenticated inputCapture card PINassociate()form()input()tokenize()
Full-screen verificationKYC, KYB, Stakeholder due diligenceconsumer_kyc().init() / kyb().init() with callbacks

Input + tokenize pattern

Used for collecting sensitive credentials (passwords, passcodes). The SDK captures input securely and returns a token for server-side use.

import React, { useEffect, useRef, useState } from "react";

const PasswordInput: React.FC = () => {
const formRef = useRef<any>(null);
const inputRef = useRef<HTMLDivElement>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);

useEffect(() => {
if (!window.OpcUxSecureClient) {
setError("SDK not loaded");
return;
}

const form = window.OpcUxSecureClient.form();

// For Password use: form.input("password", "password", options)
// For Passcode use: form.input("passcode", "passCode", options)
// For Confirm Password use: form.input("confirmPassword", "confirmPassword", options)
const input = form.input("password", "password", {
placeholder: "Enter your password",
maxlength: 50,
style: {
base: { fontSize: "16px", color: "#333" },
invalid: { color: "#dc3545" },
},
});

formRef.current = form;
input.mount(inputRef.current);

// Optional: Handle enter key
input.on("submit", () => handleSubmit());

return () => {
try {
form.destroy();
} catch (e) {
console.warn("Error destroying form:", e);
}
};
}, []);

const handleSubmit = async () => {
if (!formRef.current) return;

setIsLoading(true);
setError(null);

formRef.current.tokenize(
async (tokens: Record<string, string>) => {
try {
const response = await fetch("/api/auth/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ password: tokens.password }),
});

if (!response.ok) throw new Error("Authentication failed");
console.log("Success");
} catch (e) {
setError(e instanceof Error ? e.message : "Failed");
} finally {
setIsLoading(false);
}
},
(err: any) => {
setError(err?.message || "Tokenization failed");
setIsLoading(false);
}
);
};

return (
<form
onSubmit={(e) => {
e.preventDefault();
handleSubmit();
}}
>
<div ref={inputRef} className="weavr-input" />
{error && <p className="error">{error}</p>}
<button type="submit" disabled={isLoading}>
{isLoading ? "Submitting..." : "Submit"}
</button>
</form>
);
};

Authenticated display pattern

Used for displaying sensitive card data (card number, CVVCVV Card Verification Value - the 3-digit security code printed on a payment card, used to authenticate card-not-present transactions. Weavr returns CVV in tokenized form on `GET /managed_cards/{id}` (with a stepped-up token); the value is only detokenized inside the SDK's secure CVV display component., PINPIN Personal Identification Number - the numeric code a cardholder enters to authorize chip-and-PIN purchases and ATM withdrawals. PIN is only present on physical managed cards. Weavr returns it tokenized on `GET /managed_cards/{id}` (with a stepped-up token), and the SDK detokenizes it inside a secure PIN display component.). Requires user authentication via associate() before the data can be shown.

import React, { useEffect, useRef, useState } from "react";

interface CardNumberDisplayProps {
authToken: string;
cardToken: string;
}

const CardNumberDisplay: React.FC<CardNumberDisplayProps> = ({
authToken,
cardToken,
}) => {
const spanRef = useRef<any>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const containerId = useRef(`card-number-${Date.now()}`);

useEffect(() => {
if (!window.OpcUxSecureClient) {
setError("SDK not loaded");
return;
}

// Must authenticate before displaying sensitive data
window.OpcUxSecureClient.associate(
`Bearer ${authToken}`,
() => {
// Create span to display card number
// For Card Number use: span("cardNumber", token)
// For CVV use: span("cvv", token)
// For Card PIN use: span("cardPin", token)
const span = window.OpcUxSecureClient.span("cardNumber", cardToken, {
style: {
fontSize: "18px",
fontFamily: "monospace",
letterSpacing: "2px",
},
});

spanRef.current = span;
span.mount(document.getElementById(containerId.current));
setIsLoading(false);
},
(e) => {
console.error("Association failed:", e);
setError("Authentication failed");
setIsLoading(false);
}
);

return () => {
if (spanRef.current && typeof spanRef.current.unmount === "function") {
try {
spanRef.current.unmount();
} catch (e) {
console.error("Error unmounting:", e);
}
}
};
}, [authToken, cardToken]);

if (isLoading) return <span>Loading...</span>;
if (error) return <span className="error">{error}</span>;

return <div id={containerId.current} className="secure-display" />;
};

Authenticated input pattern

Used for capturing card PINsPIN Personal Identification Number - the numeric code a cardholder enters to authorize chip-and-PIN purchases and ATM withdrawals. PIN is only present on physical managed cards. Weavr returns it tokenized on `GET /managed_cards/{id}` (with a stepped-up token), and the SDK detokenizes it inside a secure PIN display component.. Combines authentication (associate()) with the input/tokenizeTokenize Replace a card's primary account number (PAN) with a unique digital token that stands in for the real card during a transaction. When a cardholder adds a card to Apple Pay or Google Pay via push provisioning, the wallet provider stores a device-specific token rather than the underlying PAN, so the real card number isn't exposed on the device or shared with merchants. pattern.

import React, { useEffect, useRef, useState } from "react";

interface CardPinCaptureProps {
authToken: string;
onPinSet: (token: string) => void;
}

const CardPinCapture: React.FC<CardPinCaptureProps> = ({
authToken,
onPinSet,
}) => {
const formRef = useRef<any>(null);
const inputRef = useRef<HTMLDivElement>(null);
const [isReady, setIsReady] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);

useEffect(() => {
if (!window.OpcUxSecureClient) {
setError("SDK not loaded");
return;
}

// First authenticate
window.OpcUxSecureClient.associate(
`Bearer ${authToken}`,
() => {
// Then create PIN input
const form = window.OpcUxSecureClient.form();
const input = form.input("cardPin", "cardPin", {
placeholder: "Enter 4-digit PIN",
});

formRef.current = form;
input.mount(inputRef.current);
input.on("submit", () => handleSetPin());
setIsReady(true);
},
(e) => {
console.error("Associate failed:", e);
setError("Authentication failed");
}
);

return () => {
if (formRef.current && typeof formRef.current.destroy === "function") {
try {
formRef.current.destroy();
} catch (e) {
console.error("Error unmounting:", e);
}
}
};
}, [authToken]);

const handleSetPin = () => {
if (!formRef.current) return;

setIsLoading(true);
formRef.current.tokenize(
(tokens: Record<string, string>) => {
setIsLoading(false);
onPinSet(tokens.cardPin);
},
(err: any) => {
setIsLoading(false);
setError(err?.message || "Failed to set PIN");
}
);
};

return (
<div>
<div ref={inputRef} className="pin-input" />
{error && <p className="error">{error}</p>}
<button onClick={handleSetPin} disabled={!isReady || isLoading}>
{isLoading ? "Setting..." : "Set PIN"}
</button>
</div>
);
};

Full-screen verification pattern

Used for KYCKYC Know Your Customer - the identity verification process for consumer identities. This process allows you to seamlessly and securely verify your user's identity. Weavr will ask users to submit the necessary information and documentation so that they can get approved by financial providers./KYBKYB Know Your Business - the identity verification process for corporate identities. This process allows you to seamlessly and securely verify your business customer's identity. Weavr will ask users to submit the necessary information and documentation so that they can get approved by financial providers. identity verification flows. These components take over a container element and manage their own UI.

tip

KYBKYB Know Your Business - the identity verification process for corporate identities. This process allows you to seamlessly and securely verify your business customer's identity. Weavr will ask users to submit the necessary information and documentation so that they can get approved by financial providers. and stakeholder due diligence follow the same pattern with minor differences:

  • KYBKYB Know Your Business - the identity verification process for corporate identities. This process allows you to seamlessly and securely verify your business customer's identity. Weavr will ask users to submit the necessary information and documentation so that they can get approved by financial providers.: uses kyb().init() instead of consumer_kyc().init()
  • Stakeholder due diligence: uses kyc().init() and doesn't require associate() (accessed via email link)
import React, { useEffect, useState } from "react";

interface KycVerificationProps {
authToken: string;
reference: string;
onComplete: () => void;
onError: (error: string) => void;
}

const KycVerification: React.FC<KycVerificationProps> = ({
authToken,
reference,
onComplete,
onError,
}) => {
const [isLoading, setIsLoading] = useState(true);

useEffect(() => {
if (!window.OpcUxSecureClient) {
onError("SDK not loaded");
return;
}

window.OpcUxSecureClient.associate(
`Bearer ${authToken}`,
() => {
// For KYB use: window.OpcUxSecureClient.kyb().init(selector, options, callback, config)
window.OpcUxSecureClient.consumer_kyc().init({
selector: "#kyc-container",
reference: reference,
lang: "en",

onMessage: (message: string, additionalInfo: any) => {
console.log("KYC message:", message, additionalInfo);
if (message === "kycSubmitted") {
onComplete();
}
},

onError: (error: string) => {
console.error("KYC error:", error);
onError(error);
},
});

setIsLoading(false);
},
(error) => {
console.error("Association failed:", error);
onError("Failed to authenticate");
setIsLoading(false);
}
);

// Note: Full-screen components handle their own cleanup
}, [authToken, reference]);

return (
<div>
{isLoading && <div className="loading">Loading verification...</div>}
<div id="kyc-container" style={{ minHeight: 500 }} />
</div>
);
};
Stakeholder due diligence exception

kyc().init() (used for the stakeholder due diligence flow) does not require authentication via associate(). Each stakeholder receives a verification link via email containing the reference token:

// Director KYC - no associate() needed
window.OpcUxSecureClient.kyc().init(
"#kyc-container",
{ reference: "director-ref-123" },
(messageType: string, payload: any) => {
console.log("Director KYC:", messageType, payload);
},
{ lang: "en" }
);

Authentication context

For components requiring authentication, use React Context to share auth state:

// src/context/WeavrAuthContext.tsx
import React, { createContext, useContext, useState, useCallback } from "react";

interface WeavrAuthContextType {
isAuthenticated: boolean;
isLoading: boolean;
error: string | null;
authenticate: (token: string) => Promise<void>;
logout: () => void;
}

const WeavrAuthContext = createContext<WeavrAuthContextType | null>(null);

export const WeavrAuthProvider: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);

const authenticate = useCallback((token: string): Promise<void> => {
setIsLoading(true);
setError(null);

return new Promise((resolve, reject) => {
window.OpcUxSecureClient.associate(
`Bearer ${token}`,
() => {
setIsLoading(false);
setIsAuthenticated(true);
resolve();
},
(e: Error) => {
setIsLoading(false);
setError(e.message);
reject(e);
}
);
});
}, []);

const logout = useCallback(() => {
setIsAuthenticated(false);
}, []);

return (
<WeavrAuthContext.Provider
value={{ isAuthenticated, isLoading, error, authenticate, logout }}
>
{children}
</WeavrAuthContext.Provider>
);
};

export const useWeavrAuth = (): WeavrAuthContextType => {
const context = useContext(WeavrAuthContext);
if (!context) {
throw new Error("useWeavrAuth must be used within WeavrAuthProvider");
}
return context;
};

Usage:

// Wrap your app
<WeavrAuthProvider>
<App />
</WeavrAuthProvider>;

// In components
const { isAuthenticated, authenticate, logout } = useWeavrAuth();

Next steps