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
- Overview
- Error Handling Architecture
- Custom Error Types
- Error Handling Components
- Service Error Handling
- Best Practices
- Examples
- 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
- Error Occurs → Custom error types categorize the error
- Error Handler → Applies appropriate strategy (retry, fallback, escalate)
- Error Boundary → Catches React component errors
- 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
- RETRY: Retry the operation with exponential backoff
- FALLBACK: Use a fallback value or operation
- IGNORE: Log the error but continue execution
- ESCALATE: Report to monitoring systems
- 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
- React Error Boundaries Documentation
- JavaScript Error Handling Best Practices
- TypeScript Error Handling Patterns
Note: Remember to customize error messages, error codes, and handling strategies based on your specific plugin requirements and user needs.