Skip to main content

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:

  1. Remove any import statements for the Weavr SDK
  2. Add the SDK script tag to your index.html:
<script src="https://sandbox.weavr.io/app/secure/static/client.1.js"></script>
  1. Access the SDK via window.OpcUxSecureClient

Component not rendering

Symptoms: empty container, no visible input

Possible causes and solutions:

  1. SDK not loaded: check window.OpcUxSecureClient exists before using it
  2. Mounting before DOM ready: ensure mount() is called inside useEffect()
  3. Container ref is null: verify the ref is attached to the DOM element
  4. 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:

  1. User left input empty: add validation before tokenizing
  2. Input validation failed: the SDK validates input format internally
  3. 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:

  1. Invalid auth token: verify the token is valid and not expired
  2. Network issues: check browser network tab for failed requests
  3. 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]);