Robust error handling for A/B test variants with automatic fallbacks and error tracking.
The Xaiku SDK provides comprehensive error handling to ensure variants don't break your application. When variant content fails, the system gracefully falls back to safe defaults while tracking errors for analysis.
Catches JavaScript errors in variant components and provides fallback UI:
import { XaikuErrorBoundary } from '@xaiku/react'
function App() {
return (
<XaikuErrorBoundary
experimentId="hero-test"
variantId="variant-a"
partId="hero-section"
fallback={<div>Something went wrong. Please try again.</div>}
errorMessage="Hero section unavailable"
onError={(error, errorInfo, context) => {
console.log('Variant error:', error.message)
console.log('Context:', context)
}}
>
<HeroSection />
</XaikuErrorBoundary>
)
}Props:
experimentId- A/B test experiment identifiervariantId- Specific variant (optional, auto-detected)partId- Component part identifierfallback- Custom fallback JSX elementerrorMessage- Default error message stringonError- Error callback function
Error-safe version of the Text component with automatic fallbacks:
import { SafeText } from '@xaiku/react'
function ProductCard() {
return (
<div className="product-card">
<SafeText
experimentId="product-names"
id="product-title"
fallback="Product Name"
errorFallback={<h3>Product</h3>}
errorMessage="Title unavailable"
/>
<SafeText
experimentId="product-descriptions"
id="product-desc"
fallback="Product description"
errorFallback={<p>Description coming soon</p>}
/>
</div>
)
}Additional Props:
errorFallback- JSX element to show on error- All standard Text component props
Hook for programmatic error handling with variant context:
import { useXaikuErrorBoundary } from '@xaiku/react'
function CustomComponent() {
const handleError = useXaikuErrorBoundary({
experimentId: "custom-widget",
partId: "data-visualization"
})
useEffect(() => {
try {
// Risky operation that might fail
processVariantData()
} catch (error) {
handleError(error, {
context: 'data processing',
timestamp: Date.now()
})
}
}, [handleError])
return <div>Custom widget content</div>
}Tracks variant-related errors with performance context:
import { useTrackPerformanceImpact } from '@xaiku/react'
function MonitoredComponent() {
const { trackError } = useTrackPerformanceImpact({
experimentId: "performance-test",
partId: "heavy-component"
})
const handleAsyncError = async () => {
try {
await fetchVariantData()
} catch (error) {
trackError(error) // Automatically includes performance context
}
}
return <button onClick={handleAsyncError}>Load Data</button>
}Errors during component rendering:
function ProblematicVariant() {
// This might throw an error
const data = JSON.parse(invalidJson)
return <div>{data.message}</div>
}
// Wrap with error boundary
<XaikuErrorBoundary
experimentId="json-test"
fallback={<div>Data unavailable</div>}
>
<ProblematicVariant />
</XaikuErrorBoundary>API failures when fetching variant data:
function NetworkErrorHandler() {
const [error, setError] = useState(null)
const handleError = useXaikuErrorBoundary({
experimentId: "api-test",
partId: "data-fetch"
})
useEffect(() => {
fetchVariantData()
.catch(err => {
setError(err)
handleError(err, { type: 'network', url: err.url })
})
}, [handleError])
if (error) {
return <div>Unable to load content. Using default.</div>
}
return <div>Content loaded successfully</div>
}Invalid or missing variant configurations:
function ConfigErrorExample() {
return (
<SafeText
experimentId="nonexistent-experiment"
id="invalid-variant"
fallback="Safe fallback content"
errorFallback={<span>Configuration error - using default</span>}
/>
)
}Implement retry logic for transient errors:
function RetryableComponent() {
const [retryCount, setRetryCount] = useState(0)
const maxRetries = 3
const handleError = useXaikuErrorBoundary({
experimentId: "retry-test",
partId: "retryable-content"
})
const retryOperation = useCallback(() => {
if (retryCount < maxRetries) {
setRetryCount(prev => prev + 1)
// Retry the failed operation
} else {
handleError(new Error('Max retries exceeded'), {
retryCount,
maxRetries
})
}
}, [retryCount, maxRetries, handleError])
return (
<div>
<button onClick={retryOperation}>
Retry ({retryCount}/{maxRetries})
</button>
</div>
)
}Multiple levels of fallback content:
function ProgressiveFallback() {
const [fallbackLevel, setFallbackLevel] = useState(0)
const fallbacks = [
() => <RichVariantContent />, // Level 0: Full variant
() => <BasicVariantContent />, // Level 1: Simplified version
() => <StaticContent />, // Level 2: Static fallback
() => <div>Content unavailable</div> // Level 3: Minimal fallback
]
const CurrentContent = fallbacks[fallbackLevel] || fallbacks[3]
return (
<XaikuErrorBoundary
experimentId="progressive-test"
onError={() => {
if (fallbackLevel < fallbacks.length - 1) {
setFallbackLevel(prev => prev + 1)
}
}}
>
<CurrentContent />
</XaikuErrorBoundary>
)
}Monitor error rates by variant:
function ErrorRateMonitor() {
const [errorCount, setErrorCount] = useState(0)
const [totalRenders, setTotalRenders] = useState(0)
useEffect(() => {
setTotalRenders(prev => prev + 1)
})
const handleError = useXaikuErrorBoundary({
experimentId: "error-monitoring",
partId: "error-prone-component"
})
const trackError = (error) => {
setErrorCount(prev => prev + 1)
const errorRate = (errorCount + 1) / totalRenders
handleError(error, {
errorRate,
errorCount: errorCount + 1,
totalRenders,
timestamp: Date.now()
})
}
return (
<div>
<p>Error Rate: {((errorCount / totalRenders) * 100).toFixed(2)}%</p>
<p>Errors: {errorCount} / {totalRenders}</p>
</div>
)
}Add contextual information to error reports:
function EnrichedErrorReporting() {
const handleError = useXaikuErrorBoundary({
experimentId: "context-test",
partId: "enriched-component"
})
const enrichedErrorHandler = (error) => {
const context = {
// User context
userId: getCurrentUserId(),
sessionId: getSessionId(),
// Environment context
userAgent: navigator.userAgent,
viewport: {
width: window.innerWidth,
height: window.innerHeight
},
url: window.location.href,
// App context
timestamp: Date.now(),
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
// Performance context
memory: performance.memory ? {
used: performance.memory.usedJSHeapSize,
total: performance.memory.totalJSHeapSize
} : null
}
handleError(error, context)
}
return (
<XaikuErrorBoundary
experimentId="enriched-test"
onError={enrichedErrorHandler}
>
<ComplexVariantComponent />
</XaikuErrorBoundary>
)
}Place error boundaries at component level for isolation:
// Good - isolates each variant component
function ProductPage() {
return (
<div>
<XaikuErrorBoundary experimentId="product-header">
<ProductHeader />
</XaikuErrorBoundary>
<XaikuErrorBoundary experimentId="product-details">
<ProductDetails />
</XaikuErrorBoundary>
<XaikuErrorBoundary experimentId="product-reviews">
<ProductReviews />
</XaikuErrorBoundary>
</div>
)
}
// Avoid - one boundary affects entire page
function ProductPage() {
return (
<XaikuErrorBoundary experimentId="product-page">
<div>
<ProductHeader />
<ProductDetails />
<ProductReviews />
</div>
</XaikuErrorBoundary>
)
}Provide fallbacks that maintain user flow:
// Good - maintains shopping experience
<SafeText
id="add-to-cart-button"
fallback="Add to Cart"
errorFallback={<button>Add to Cart</button>}
/>
// Avoid - breaks user experience
<SafeText
id="add-to-cart-button"
fallback="Add to Cart"
errorFallback={<div>Error occurred</div>}
/>Integrate with error tracking services:
function ErrorTrackingSetup() {
const handleError = useXaikuErrorBoundary({
experimentId: "error-tracking",
partId: "monitored-component"
})
const customErrorHandler = (error, errorInfo, context) => {
// Track in Xaiku
handleError(error, { ...context, errorInfo })
// Also track in external services
if (typeof window !== 'undefined') {
// Sentry
window.Sentry?.captureException(error, {
tags: { variant: context.variantId },
extra: context
})
// LogRocket
window.LogRocket?.captureException(error)
// Custom analytics
window.analytics?.track('Variant Error', {
experimentId: context.experimentId,
variantId: context.variantId,
error: error.message
})
}
}
return (
<XaikuErrorBoundary onError={customErrorHandler}>
<VariantComponent />
</XaikuErrorBoundary>
)
}Create components to test error handling:
function ErrorSimulator({ shouldError = false, errorType = 'render' }) {
useEffect(() => {
if (shouldError && errorType === 'effect') {
throw new Error('Simulated useEffect error')
}
}, [shouldError, errorType])
if (shouldError && errorType === 'render') {
throw new Error('Simulated render error')
}
return <div>Normal content</div>
}
// Test error boundary
function ErrorBoundaryTest() {
const [simulateError, setSimulateError] = useState(false)
return (
<div>
<button onClick={() => setSimulateError(!simulateError)}>
Toggle Error Simulation
</button>
<XaikuErrorBoundary
experimentId="error-test"
fallback={<div>Error handled gracefully</div>}
>
<ErrorSimulator shouldError={simulateError} />
</XaikuErrorBoundary>
</div>
)
}Set up comprehensive error monitoring for production:
// Error monitoring dashboard
function ErrorMonitoringDashboard() {
const [errorStats, setErrorStats] = useState({
totalErrors: 0,
errorsByVariant: {},
errorsByExperiment: {},
recentErrors: []
})
const trackError = useCallback((error, context) => {
setErrorStats(prev => ({
totalErrors: prev.totalErrors + 1,
errorsByVariant: {
...prev.errorsByVariant,
[context.variantId]: (prev.errorsByVariant[context.variantId] || 0) + 1
},
errorsByExperiment: {
...prev.errorsByExperiment,
[context.experimentId]: (prev.errorsByExperiment[context.experimentId] || 0) + 1
},
recentErrors: [
{ error: error.message, context, timestamp: Date.now() },
...prev.recentErrors.slice(0, 9) // Keep last 10 errors
]
}))
}, [])
return (
<div className="error-dashboard">
<h3>Error Statistics</h3>
<p>Total Errors: {errorStats.totalErrors}</p>
<h4>Errors by Experiment</h4>
<ul>
{Object.entries(errorStats.errorsByExperiment).map(([experiment, count]) => (
<li key={experiment}>{experiment}: {count} errors</li>
))}
</ul>
<h4>Recent Errors</h4>
<ul>
{errorStats.recentErrors.map((err, index) => (
<li key={index}>
{err.error} - {err.context.experimentId} - {new Date(err.timestamp).toLocaleTimeString()}
</li>
))}
</ul>
</div>
)
}