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, CVV, PIN):
// 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()
};
}
Spans 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({ selector, reference, onMessage, onError })
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 })
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 })
// 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.
| Pattern | Components | Key Characteristics |
|---|---|---|
| Input + Tokenize | Password, Confirm Password, Passcode, Confirm Passcode | form() → input() → mount() → tokenize() |
| Authenticated Display | Card Number, CVV, Show Card PIN | associate() → span() → mount() |
| Authenticated Input | Capture Card PIN | associate() → form() → input() → tokenize() |
| Full-screen Verification | KYC, KYB, Director KYC | consumer_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, CVV, PIN). 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 PINs. Combines authentication (associate()) with the input/tokenize 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 KYC/KYB identity verification flows. These components take over a container element and manage their own UI.
KYB and Director KYC follow the same pattern with minor differences:
- KYB: uses
kyb().init()instead ofconsumer_kyc().init() - Director KYC: uses
kyc().init()and does not requireassociate()(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>
);
};
kyc().init() (Director KYC / Beneficiaries KYC) does not require authentication via associate(). Directors receive 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
- Reusable components—Production-ready wrapper components built on these patterns
- Troubleshooting—Common issues and solutions