React integration: troubleshooting
This page covers error handling patterns and common issues when integrating the Weavr SDK with React. For setup instructions, see Getting started.
Error handling
SDK initialization errors
import React, { useEffect, useState } from "react";
interface WeavrSDKGuardProps {
uiKey: string;
children: React.ReactNode;
}
const WeavrSDKGuard: React.FC<WeavrSDKGuardProps> = ({ uiKey, children }) => {
const [error, setError] = useState<string | null>(null);
const [isReady, setIsReady] = useState(false);
useEffect(() => {
if (!window.OpcUxSecureClient) {
setError(
"Weavr SDK failed to load. Check your internet connection and ensure the script tag is in index.html."
);
return;
}
try {
window.OpcUxSecureClient.init(uiKey);
setIsReady(true);
} catch (e) {
setError(
`SDK initialization failed: ${e instanceof Error ? e.message : "Unknown error"}`
);
}
}, [uiKey]);
if (error) return <div className="error-banner">{error}</div>;
if (!isReady) return <div>Initializing...</div>;
return <>{children}</>;
};
Component mount errors
import React, { useEffect, useRef, useState } from "react";
const SecureInput: React.FC = () => {
const containerRef = useRef<HTMLDivElement>(null);
const formRef = useRef<any>(null);
const [mountError, setMountError] = useState<string | null>(null);
useEffect(() => {
try {
const form = window.OpcUxSecureClient.form();
const input = form.input("password", "password", {
placeholder: "Password",
});
formRef.current = form;
if (!containerRef.current) {
throw new Error("Container element not found");
}
input.mount(containerRef.current);
} catch (e) {
setMountError(
e instanceof Error ? e.message : "Failed to mount component"
);
console.error("Mount error:", e);
}
return () => {
try {
formRef.current?.destroy();
} catch (e) {
console.warn("Cleanup warning:", e);
}
};
}, []);
if (mountError) return <div className="error">{mountError}</div>;
return <div ref={containerRef} />;
};
Tokenization errors
const handleSubmit = () => {
formRef.current?.tokenize(
(tokens: Record<string, string>) => {
if (!tokens.password) {
// Empty token usually means validation failed
setError("Please enter a valid password");
return;
}
// Process tokens...
},
(e: any) => {
setError(`Tokenization failed: ${e?.message || "Unknown error"}`);
}
);
};
React error boundary
Create an error boundary component for Weavr integrations:
import React, { Component, ErrorInfo, ReactNode } from "react";
interface Props {
children: ReactNode;
fallback?: ReactNode;
}
interface State {
hasError: boolean;
error: Error | null;
}
class WeavrErrorBoundary extends Component<Props, State> {
state: State = { hasError: false, error: null };
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.error("Weavr component error:", error, errorInfo);
}
handleRetry = () => {
this.setState({ hasError: false, error: null });
};
render() {
if (this.state.hasError) {
return (
this.props.fallback || (
<div className="error-boundary">
<p>Something went wrong with the secure component.</p>
<button onClick={this.handleRetry}>Try Again</button>
</div>
)
);
}
return this.props.children;
}
}
export default WeavrErrorBoundary;
Usage:
<WeavrErrorBoundary>
<PasswordInput />
</WeavrErrorBoundary>
Common issues and solutions
Error: "TypeError: P.has is not a function"
Symptoms: error appears in console immediately when component mounts.
Cause: the Weavr SDK was imported through a JavaScript bundler (Vite, Webpack) instead of being loaded via a script tag.
Solution:
- Remove any
importstatements for the Weavr SDK - Add the SDK script tag to your
index.html:
<script src="https://sandbox.weavr.io/app/secure/static/client.1.js"></script>
- Access the SDK via
window.OpcUxSecureClient
Component not rendering
Symptoms: empty container, no visible input
Possible causes and solutions:
- SDK not loaded: check
window.OpcUxSecureClientexists before using it - Mounting before DOM ready: ensure
mount()is called insideuseEffect() - Container ref is null: verify the ref is attached to the DOM element
- Missing dimensions: ensure the container has a min-height
useEffect(() => {
console.log("Container:", containerRef.current); // Debug: should not be null
console.log("SDK:", window.OpcUxSecureClient); // Debug: should exist
}, []);
Component disappears after navigation
Symptoms: component works initially but disappears when navigating away and back.
Cause: the SDK component wasn't properly cleaned up, causing state issues.
Solution: always clean up in the useEffect return function:
useEffect(() => {
const form = window.OpcUxSecureClient.form();
const input = form.input("password", "password", {});
input.mount(containerRef.current);
return () => {
// Critical: Clean up to prevent state issues
try {
form.destroy();
} catch (e) {
console.warn("Error destroying form:", e);
}
};
}, []);
For route-based components with React Router, use a key to force remounting:
<Route path="/settings" element={<PasswordPage key={location.pathname} />} />
Cleanup errors on unmount
Symptoms: console errors when navigating away
Solution: always use defensive cleanup:
// For forms — use destroy()
return () => {
try {
formRef.current?.destroy();
} catch (e) {
console.warn("Error destroying form:", e);
}
};
// For spans — use unmount()
return () => {
try {
spanRef.current?.unmount();
} catch (e) {
console.warn("Error unmounting span:", e);
}
};
Token is undefined after submit
Symptoms: tokens.password is undefined in the tokenize callback.
Possible causes:
- User left input empty: add validation before tokenizing
- Input validation failed: the SDK validates input format internally
- Async timing issue: token is only available inside the callback
Solution:
const handleSubmit = () => {
formRef.current?.tokenize(
(tokens: Record<string, string>) => {
// Check inside callback
if (!tokens.password) {
setError("Please enter a valid password");
return;
}
// Safe to use token here
submitToServer(tokens.password);
},
(error: any) => {
setError(`Tokenization failed: ${error?.message}`);
}
);
// WRONG: tokens not available here
// submitToServer(tokens.password)
};
React Strict Mode double mounting
Symptoms: component mounts twice in development
Solution: this is expected behaviour in React 18+ Strict Mode. Use refs to track mounting state:
const mountedRef = useRef(false);
useEffect(() => {
if (mountedRef.current) return;
mountedRef.current = true;
// Mount SDK component...
return () => {
mountedRef.current = false;
// Cleanup...
};
}, []);
Memory leaks
Symptoms: memory usage increases over time, especially with repeated navigation.
Cause: the SDK components are not being cleaned up when React components unmount.
Solution: always clean up in the useEffect return function:
const SecureForm: React.FC = () => {
const formRef = useRef<any>(null);
useEffect(() => {
const form = window.OpcUxSecureClient.form();
formRef.current = form;
const password = form.input("password", "password", {});
password.mount(document.getElementById("password-container"));
const confirmPassword = form.input(
"confirmPassword",
"confirmPassword",
{}
);
confirmPassword.mount(document.getElementById("confirm-container"));
return () => {
// Clean up the entire form (destroys all inputs)
try {
formRef.current?.destroy();
} catch (e) {
console.warn("Error destroying form:", e);
}
formRef.current = null;
};
}, []);
return (
<div>
<div id="password-container" />
<div id="confirm-container" />
</div>
);
};
Container ID conflicts
Symptoms: only first component renders when using multiple instances
Solution: use unique IDs with timestamps:
const containerId = useRef(`weavr-password-${Date.now()}-${Math.random()}`);
SDK iframe initialization warning
Symptoms: console warning: "Attempting to use 'onMessage' for <inline-menu-ready> without registering a handler"
Cause: the SDK's iframe communication handlers are not fully registered before the component mounts.
Solution: add a small delay before mounting to allow SDK initialization:
useEffect(() => {
if (!window.OpcUxSecureClient) {
console.error("SDK not loaded");
return;
}
const form = window.OpcUxSecureClient.form();
const input = form.input("password", "password", {
placeholder: "Enter password",
});
// Add delay to let SDK fully initialize iframe handlers
const timer = setTimeout(() => {
input.mount(containerRef.current);
}, 100);
return () => {
clearTimeout(timer);
try {
form.destroy();
} catch (e) {
console.warn("Error destroying form:", e);
}
};
}, []);
Note: this warning is cosmetic and doesn't prevent the component from functioning correctly, but adding the delay eliminates it.
Associate callback not firing
Symptoms: code after associate() never executes.
Possible causes:
- Invalid auth token: verify the token is valid and not expired
- Network issues: check browser network tab for failed requests
- Wrong environment: sandbox token used with Live SDK URL (or vice versa)
Solution: always provide an error callback:
useEffect(() => {
window.OpcUxSecureClient.associate(
`Bearer ${authToken}`,
() => {
console.log("Associate succeeded");
// Continue setup...
},
(error) => {
console.error("Associate failed:", error);
setError("Authentication failed. Please try again.");
}
);
}, [authToken]);