Error Handling & API Abstraction Architecture
📖 Concept
Error handling architecture in React Native must cover multiple failure domains:
- Network errors — No connectivity, timeouts, 5xx errors
- API errors — 4xx responses, validation errors, auth failures
- Runtime errors — JS exceptions, native crashes, unhandled rejections
- State errors — Corrupted state, impossible state transitions
- Navigation errors — Invalid routes, deep link failures
Principles of production error handling:
- Errors are data, not exceptions — Model errors as part of your state (discriminated unions)
- Fail gracefully, not silently — Always inform the user AND log for debugging
- Centralize error reporting — One error reporting pipeline (Sentry, Crashlytics)
- Isolate failures — Error boundary per feature so one crash doesn't take down the app
- Retry with backoff — Network failures should auto-retry with exponential backoff
API abstraction layer:
Don't scatter fetch() calls throughout your components. Build a centralized API client that handles:
- Authentication (token injection, refresh)
- Error normalization (different APIs return errors differently)
- Request/response interceptors (logging, analytics)
- Retry logic (with backoff)
- Offline queuing
- Response caching
- Request deduplication
💻 Code Example
1// === PRODUCTION ERROR HANDLING ARCHITECTURE ===23// 1. Error type hierarchy4class AppError extends Error {5 constructor(6 message: string,7 public code: string,8 public severity: 'critical' | 'error' | 'warning' | 'info',9 public userMessage?: string,10 public metadata?: Record<string, any>,11 ) {12 super(message);13 this.name = 'AppError';14 }15}1617class NetworkError extends AppError {18 constructor(19 message: string,20 public statusCode?: number,21 public retryable: boolean = true,22 ) {23 super(message, 'NETWORK_ERROR', statusCode && statusCode >= 500 ? 'critical' : 'error');24 }25}2627class AuthError extends AppError {28 constructor(message: string) {29 super(message, 'AUTH_ERROR', 'error', 'Please sign in again');30 }31}3233class ValidationError extends AppError {34 constructor(35 message: string,36 public fields: Record<string, string[]>,37 ) {38 super(message, 'VALIDATION_ERROR', 'warning');39 }40}4142// 2. Central error reporter43class ErrorReporter {44 private static instance: ErrorReporter;4546 static getInstance(): ErrorReporter {47 if (!ErrorReporter.instance) {48 ErrorReporter.instance = new ErrorReporter();49 }50 return ErrorReporter.instance;51 }5253 report(error: Error, context?: Record<string, any>) {54 // Always log locally55 console.error('[ErrorReporter]', error.message, context);5657 if (error instanceof AppError) {58 // Structured error — send with metadata59 Sentry.captureException(error, {60 tags: { errorCode: error.code, severity: error.severity },61 extra: { ...error.metadata, ...context },62 });6364 // Critical errors alert on-call65 if (error.severity === 'critical') {66 this.alertOnCall(error);67 }68 } else {69 // Unexpected error — always critical70 Sentry.captureException(error, { extra: context });71 }72 }7374 private alertOnCall(error: AppError) {75 // PagerDuty, Opsgenie, etc.76 }77}7879// 3. API Client with interceptors and retry80class APIClient {81 private interceptors: RequestInterceptor[] = [];8283 constructor(84 private config: APIConfig,85 private errorReporter: ErrorReporter,86 ) {87 // Default interceptors88 this.addInterceptor(new AuthInterceptor());89 this.addInterceptor(new LoggingInterceptor());90 }9192 async request<T>(endpoint: string, options: RequestOptions = {}): Promise<T> {93 const { retries = 3, retryDelay = 1000, ...fetchOptions } = options;9495 // Apply interceptors96 let config = { url: `${this.config.baseURL}${endpoint}`, ...fetchOptions };97 for (const interceptor of this.interceptors) {98 config = await interceptor.onRequest(config);99 }100101 // Retry with exponential backoff102 for (let attempt = 0; attempt <= retries; attempt++) {103 try {104 const response = await fetch(config.url, config);105106 if (response.status === 401) {107 throw new AuthError('Token expired');108 }109110 if (!response.ok) {111 const body = await response.json().catch(() => ({}));112 throw new NetworkError(113 body.message || `HTTP ${response.status}`,114 response.status,115 response.status >= 500, // 5xx = retryable116 );117 }118119 return response.json();120 } catch (error) {121 if (error instanceof AuthError) {122 // Try token refresh once123 const refreshed = await this.refreshToken();124 if (refreshed) continue; // Retry with new token125 throw error; // Refresh failed126 }127128 if (error instanceof NetworkError && error.retryable && attempt < retries) {129 // Exponential backoff: 1s, 2s, 4s130 await this.delay(retryDelay * Math.pow(2, attempt));131 continue;132 }133134 this.errorReporter.report(error as Error, { endpoint, attempt });135 throw error;136 }137 }138139 throw new NetworkError('Max retries exceeded', undefined, false);140 }141142 private delay(ms: number): Promise<void> {143 return new Promise(resolve => setTimeout(resolve, ms));144 }145}
🏋️ Practice Exercise
Error Handling Exercises:
- Implement an API client with retry logic and exponential backoff
- Create a global error boundary that catches rendering errors and reports to Sentry
- Build a network status monitor that shows an offline banner and queues failed requests
- Implement token refresh logic that transparently retries the failed request
- Create error types for all failure modes in your app and map them to user-facing messages
- Build a debug screen that shows recent errors, network requests, and state snapshots
⚠️ Common Mistakes
Catching errors silently —
catch (e) {}hides bugs; always log or report errors even if you handle them gracefullyShowing raw error messages to users — 'TypeError: Cannot read property x of undefined' is not a user message
Not implementing retry logic for transient network failures — user gets an error on a flaky connection instead of automatic recovery
Not isolating failures with error boundaries — one component's render error crashes the entire app
Inconsistent error handling — some API calls have try/catch, others don't; some show alerts, others show inline errors
💼 Interview Questions
🎤 Mock Interview
Mock interview is powered by AI for Error Handling & API Abstraction Architecture. Login to unlock this feature.