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, 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()
};
}
Span styles vs input styles

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.

PatternComponentsKey Characteristics
Input + TokenizePassword, 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, Director KYCconsumer_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.

tip

KYB and Director KYC follow the same pattern with minor differences:

  • KYB: uses kyb().init() instead of consumer_kyc().init()
  • Director KYC: uses kyc().init() and does not 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>
);
};
Director KYC exception

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