Skip to main content

Error Handling Best Practices Guide

This guide provides comprehensive information about error handling patterns and best practices implemented in the BrainDrive Plugin Template.

Table of Contents

  1. Overview
  2. Error Handling Architecture
  3. Custom Error Types
  4. Error Handling Components
  5. Service Error Handling
  6. Best Practices
  7. Examples
  8. Testing Error Handling

Overview

The Plugin Template implements a comprehensive error handling system that includes:

  • Custom Error Types: Specific error classes for different error categories
  • Error Boundaries: React components that catch and handle component errors
  • Error Handler Utility: Centralized error handling with retry logic and fallbacks
  • Service Integration: Error handling integrated into all service calls
  • User-Friendly Display: Enhanced error display components with detailed information

Error Handling Architecture

Core Components

src/
├── utils/
│ └── errorHandling.ts # Core error handling utilities
├── components/
│ ├── ErrorDisplay.tsx # Enhanced error display component
│ └── ErrorBoundary.tsx # React error boundary component
├── services/
│ └── PluginService.ts # Service with integrated error handling
└── PluginTemplate.tsx # Main component with error handling

Error Flow

  1. Error Occurs → Custom error types categorize the error
  2. Error Handler → Applies appropriate strategy (retry, fallback, escalate)
  3. Error Boundary → Catches React component errors
  4. Error Display → Shows user-friendly error messages with actions

Custom Error Types

PluginError (Base Class)

class PluginError extends Error {
public readonly code: string;
public readonly details?: any;
public readonly timestamp: string;
public readonly recoverable: boolean;
}

Usage:

throw new PluginError(
'Operation failed',
'OPERATION_ERROR',
{ context: 'user-action' },
true // recoverable
);

ServiceError

For service-related errors (API, Event Service, etc.)

throw new ServiceError(
'API service unavailable',
'api',
'SERVICE_UNAVAILABLE',
{ endpoint: '/api/data' }
);

ValidationError

For input validation errors

throw new ValidationError(
'Invalid email format',
'email',
'user@invalid'
);

NetworkError

For network and API communication errors

throw new NetworkError(
'Request timeout',
408,
'/api/plugin-data'
);

ConfigurationError

For configuration-related errors

throw new ConfigurationError(
'Missing required configuration',
'apiEndpoint'
);

Error Handling Components

ErrorBoundary

Catches JavaScript errors in React component trees:

<ErrorBoundary
onError={(error, errorInfo) => {
console.error('Component error:', error);
}}
resetOnPropsChange={true}
resetKeys={[pluginId, moduleId]}
>
<YourComponent />
</ErrorBoundary>

Features:

  • Automatic error catching and display
  • Reset functionality for error recovery
  • Custom fallback UI support
  • Error reporting integration
  • Development debugging information

ErrorDisplay

Enhanced error display with comprehensive information:

<ErrorDisplay
error={errorInfo}
onRetry={handleRetry}
onDismiss={handleDismiss}
showDetails={true}
variant="error"
/>

Features:

  • Multiple error variants (error, warning, info)
  • Retry and dismiss actions
  • Detailed error information
  • Copy error details to clipboard
  • Responsive design

withErrorBoundary HOC

Higher-order component for easy error boundary integration:

const SafeComponent = withErrorBoundary(MyComponent, {
onError: (error, errorInfo) => console.log('Error:', error)
});

Service Error Handling

ErrorHandler Class

Centralized error handling with multiple strategies:

const errorHandler = new ErrorHandler({
maxRetries: 3,
retryDelay: 1000,
enableLogging: true,
enableReporting: true
});

// Safe async operation
const result = await errorHandler.safeAsync(
async () => await riskyOperation(),
fallbackValue,
ErrorStrategy.RETRY
);

Error Strategies

  1. RETRY: Retry the operation with exponential backoff
  2. FALLBACK: Use a fallback value or operation
  3. IGNORE: Log the error but continue execution
  4. ESCALATE: Report to monitoring systems
  5. USER_ACTION: Require user intervention

Service Integration

All service methods use error handling:

async fetchData(): Promise<PluginData> {
return this.errorHandler.safeAsync(async () => {
// Validate service availability
if (!this.apiService) {
throw new ServiceError('API service not available', 'api');
}

// Make API call with validation
const response = await this.apiService.get('/api/data');
this.validateData(response.data);

return response.data;
}, null, ErrorStrategy.RETRY);
}

Best Practices

1. Use Appropriate Error Types

// ✅ Good - Specific error type
throw new ValidationError('Invalid email', 'email', userInput);

// ❌ Bad - Generic error
throw new Error('Something went wrong');

2. Provide Context

// ✅ Good - Rich context
throw new ServiceError(
'Failed to load user preferences',
'settings',
'LOAD_ERROR',
{ userId, attemptCount: 3 }
);

// ❌ Bad - No context
throw new Error('Load failed');

3. Handle Errors at the Right Level

// ✅ Good - Handle at component level
class MyComponent extends React.Component {
async componentDidMount() {
try {
await this.loadData();
} catch (error) {
this.handleComponentError(error, 'componentDidMount');
}
}
}

// ❌ Bad - Silent failure
async loadData() {
try {
await api.getData();
} catch (error) {
// Silent failure - user doesn't know what happened
}
}

4. Use Error Boundaries

// ✅ Good - Wrap components in error boundaries
<ErrorBoundary>
<UserInterface />
</ErrorBoundary>

// ❌ Bad - No error boundary protection
<UserInterface />

5. Provide User-Friendly Messages

// ✅ Good - User-friendly message
const userMessage = ErrorUtils.getUserMessage(error);
// "Network connection issue. Please try again."

// ❌ Bad - Technical error message
const message = error.message;
// "XMLHttpRequest failed with status 500"

Examples

Basic Error Handling

import { ErrorHandler, ErrorStrategy } from './utils/errorHandling';

const errorHandler = new ErrorHandler();

// Simple retry pattern
const data = await errorHandler.safeAsync(
() => fetchUserData(),
null,
ErrorStrategy.RETRY
);

Component Error Handling

import { ErrorBoundary, ErrorDisplay } from './components';

function MyPlugin() {
const [error, setError] = useState(null);

const handleRetry = () => {
setError(null);
// Retry logic
};

if (error) {
return (
<ErrorDisplay
error={error}
onRetry={handleRetry}
showDetails={true}
/>
);
}

return (
<ErrorBoundary>
<PluginContent />
</ErrorBoundary>
);
}

Service Error Handling

class DataService {
private errorHandler = new ErrorHandler();

async getData(id: string): Promise<Data> {
return this.errorHandler.safeAsync(async () => {
// Validate input
if (!id) {
throw new ValidationError('ID is required', 'id', id);
}

// Make API call
const response = await this.api.get(`/data/${id}`);

// Validate response
if (!response.data) {
throw new NetworkError('No data received', response.status);
}

return response.data;
});
}
}

Error Decorator

import { handleErrors, ErrorStrategy } from './utils/errorHandling';

class MyService {
@handleErrors(ErrorStrategy.RETRY)
async riskyOperation() {
// This method will automatically retry on errors
return await someRiskyApiCall();
}
}

Testing Error Handling

Unit Tests

describe('Error Handling', () => {
it('should retry failed operations', async () => {
const mockOperation = jest.fn()
.mockRejectedValueOnce(new Error('First failure'))
.mockRejectedValueOnce(new Error('Second failure'))
.mockResolvedValueOnce('Success');

const result = await errorHandler.safeAsync(
mockOperation,
null,
ErrorStrategy.RETRY
);

expect(mockOperation).toHaveBeenCalledTimes(3);
expect(result).toBe('Success');
});

it('should use fallback on max retries exceeded', async () => {
const mockOperation = jest.fn().mockRejectedValue(new Error('Always fails'));

const result = await errorHandler.safeAsync(
mockOperation,
'fallback',
ErrorStrategy.FALLBACK
);

expect(result).toBe('fallback');
});
});

Integration Tests

describe('Component Error Handling', () => {
it('should display error boundary on component error', () => {
const ThrowError = () => {
throw new Error('Test error');
};

const { getByText } = render(
<ErrorBoundary>
<ThrowError />
</ErrorBoundary>
);

expect(getByText(/Component Error/)).toBeInTheDocument();
});
});

Error Simulation

// Simulate network errors for testing
const simulateNetworkError = () => {
throw new NetworkError('Network unavailable', 0, '/api/test');
};

// Test error recovery
const testErrorRecovery = async () => {
try {
await simulateNetworkError();
} catch (error) {
const handled = await errorHandler.handleError(error, ErrorStrategy.RETRY);
expect(handled.shouldRetry).toBe(true);
}
};

Monitoring and Reporting

Error Reporting Integration

// Configure error reporting
const errorHandler = new ErrorHandler({
enableReporting: true,
// Add your error reporting service integration
});

// Custom error reporting
const reportError = async (error: Error) => {
// Send to your monitoring service
await errorReportingService.report({
error: error.message,
stack: error.stack,
context: getErrorContext(),
timestamp: new Date().toISOString()
});
};

Error Metrics

Track error patterns and frequencies:

const errorStats = errorHandler.getErrorStats();
console.log('Error statistics:', errorStats);
// { "NetworkError:timeout": 3, "ValidationError:email": 1 }

Conclusion

This comprehensive error handling system provides:

  • Robust Error Management: Multiple error types and handling strategies
  • User Experience: Friendly error messages and recovery options
  • Developer Experience: Detailed error information and debugging tools
  • Maintainability: Centralized error handling logic
  • Reliability: Automatic retry and fallback mechanisms

By following these patterns and best practices, your BrainDrive plugin will be more reliable, maintainable, and provide a better user experience when errors occur.

Additional Resources


Note: Remember to customize error messages, error codes, and handling strategies based on your specific plugin requirements and user needs.