Error Handling Implementation Patterns
1. React Error Boundaries getDerivedStateFromError
| Method | Purpose | When Called | Return Value |
|---|---|---|---|
| getDerivedStateFromError | Update state for fallback UI | After error thrown in descendant | State object to trigger re-render |
| componentDidCatch | Log error information | After error thrown, commit phase | void (side effects allowed) |
| Error Boundary | Catches React render errors | Render, lifecycle, constructors | Class component only |
| resetErrorBoundary | Reset error state | User action to retry | Re-render children |
| react-error-boundary | Library with hooks | Simplifies error boundaries | useErrorHandler hook |
Example: React Error Boundaries implementation
// Basic Error Boundary class component
import React, { Component, ErrorInfo, ReactNode } from 'react';
interface Props {
children: ReactNode;
fallback?: ReactNode;
}
interface State {
hasError: boolean;
error: Error | null;
}
class ErrorBoundary extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error: Error): State {
// Update state to trigger fallback UI
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
// Log error to service
console.error('Error caught by boundary:', error, errorInfo);
// Send to error tracking service
logErrorToService(error, errorInfo);
}
resetErrorBoundary = () => {
this.setState({ hasError: false, error: null });
};
render() {
if (this.state.hasError) {
return (
this.props.fallback || (
<div role="alert">
<h2>Something went wrong</h2>
<details>
<summary>Error details</summary>
<pre>{this.state.error?.message}</pre>
</details>
<button onClick={this.resetErrorBoundary}>
Try again
</button>
</div>
)
);
}
return this.props.children;
}
}
// Usage
function App() {
return (
<ErrorBoundary fallback={<ErrorFallback />}>
<MainContent />
</ErrorBoundary>
);
}
// Multiple error boundaries for granular handling
function Dashboard() {
return (
<div>
<ErrorBoundary fallback={<div>Header error</div>}>
<Header />
</ErrorBoundary>
<ErrorBoundary fallback={<div>Sidebar error</div>}>
<Sidebar />
</ErrorBoundary>
<ErrorBoundary fallback={<div>Main content error</div>}>
<MainContent />
</ErrorBoundary>
</div>
);
}
// react-error-boundary library (recommended)
import { ErrorBoundary, useErrorHandler } from 'react-error-boundary';
function ErrorFallback({ error, resetErrorBoundary }) {
return (
<div role="alert">
<h2>Oops! Something went wrong</h2>
<pre style={{ color: 'red' }}>{error.message}</pre>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
);
}
function App() {
return (
<ErrorBoundary
FallbackComponent={ErrorFallback}
onError={(error, errorInfo) => {
console.error('Error logged:', error, errorInfo);
}}
onReset={() => {
// Reset app state
window.location.href = '/';
}}
>
<MyApp />
</ErrorBoundary>
);
}
// useErrorHandler hook for async errors
function UserProfile({ userId }) {
const handleError = useErrorHandler();
useEffect(() => {
fetchUser(userId).catch(handleError);
}, [userId, handleError]);
return <div>Profile</div>;
}
// Error boundary with retry logic
function AppWithRetry() {
const [resetKey, setResetKey] = useState(0);
return (
<ErrorBoundary
FallbackComponent={ErrorFallback}
onReset={() => setResetKey(key => key + 1)}
resetKeys={[resetKey]}
>
<MyApp key={resetKey} />
</ErrorBoundary>
);
}
// Limitations: Error boundaries DON'T catch:
// - Event handlers (use try-catch)
// - Async code (setTimeout, promises)
// - Server-side rendering
// - Errors in error boundary itself
// Handle event handler errors
function MyComponent() {
const handleError = useErrorHandler();
const handleClick = async () => {
try {
await riskyOperation();
} catch (error) {
handleError(error); // Pass to error boundary
}
};
return <button onClick={handleClick}>Click</button>;
}
2. Vue errorCaptured Global Error Handler
| Method | Syntax | Description | Use Case |
|---|---|---|---|
| errorCaptured | errorCaptured(err, instance, info) |
Component-level error handler | Catch errors in descendants |
| app.config.errorHandler | app.config.errorHandler = fn |
Global error handler | Catch all uncaught errors |
| onErrorCaptured | onErrorCaptured(fn) |
Composition API hook | Handle errors in setup |
| Return false | Stop propagation | Prevent error from bubbling | Local error handling |
| warnHandler | app.config.warnHandler |
Handle Vue warnings | Development warnings |
Example: Vue error handling patterns
// Vue 3 global error handler
import { createApp } from 'vue';
import App from './App.vue';
const app = createApp(App);
// Global error handler
app.config.errorHandler = (err, instance, info) => {
console.error('Global error:', err);
console.log('Component:', instance);
console.log('Error info:', info);
// Send to error tracking service
logErrorToService(err, { component: instance?.$options.name, info });
};
// Global warning handler (development)
app.config.warnHandler = (msg, instance, trace) => {
console.warn('Vue warning:', msg);
console.log('Trace:', trace);
};
app.mount('#app');
// Component-level error handling with errorCaptured
<script>
export default {
name: 'ErrorBoundary',
data() {
return {
error: null,
};
},
errorCaptured(err, instance, info) {
// Capture error from child components
this.error = err;
console.error('Error captured:', err, info);
// Return false to stop propagation
return false;
},
render() {
if (this.error) {
return this.$slots.fallback?.() || 'Something went wrong';
}
return this.$slots.default?.();
},
};
</script>
// Usage
<template>
<ErrorBoundary>
<template #default>
<ChildComponent />
</template>
<template #fallback>
<div>Error occurred in child</div>
</template>
</ErrorBoundary>
</template>
// Vue 3 Composition API with onErrorCaptured
<script setup>
import { ref, onErrorCaptured } from 'vue';
const error = ref(null);
onErrorCaptured((err, instance, info) => {
error.value = err;
console.error('Error in descendant:', err, info);
// Return false to stop propagation
return false;
});
</script>
<template>
<div>
<div v-if="error" class="error">
<h3>Error: {{ error.message }}</h3>
<button @click="error = null">Reset</button>
</div>
<slot v-else />
</div>
</template>
// Async error handling in Vue
<script setup>
import { ref } from 'vue';
const data = ref(null);
const error = ref(null);
const loading = ref(false);
async function fetchData() {
loading.value = true;
error.value = null;
try {
const response = await fetch('/api/data');
if (!response.ok) throw new Error('Failed to fetch');
data.value = await response.json();
} catch (err) {
error.value = err;
console.error('Fetch error:', err);
} finally {
loading.value = false;
}
}
</script>
<template>
<div>
<button @click="fetchData" :disabled="loading">
{{ loading ? 'Loading...' : 'Fetch Data' }}
</button>
<div v-if="error" class="error">
Error: {{ error.message }}
</div>
<div v-else-if="data">
{{ data }}
</div>
</div>
</template>
// Reusable error boundary composable
import { ref, onErrorCaptured } from 'vue';
export function useErrorBoundary() {
const error = ref(null);
const errorInfo = ref(null);
onErrorCaptured((err, instance, info) => {
error.value = err;
errorInfo.value = info;
return false;
});
const reset = () => {
error.value = null;
errorInfo.value = null;
};
return { error, errorInfo, reset };
}
// Usage
<script setup>
const { error, errorInfo, reset } = useErrorBoundary();
</script>
<template>
<div v-if="error">
<h3>Error occurred</h3>
<p>{{ error.message }}</p>
<p>Info: {{ errorInfo }}</p>
<button @click="reset">Try Again</button>
</div>
<slot v-else />
</template>
3. Sentry LogRocket Error Monitoring
| Tool | Purpose | Features | Use Case |
|---|---|---|---|
| Sentry | Error tracking & monitoring | Stack traces, releases, breadcrumbs | Production error tracking |
| LogRocket | Session replay & monitoring | Video replay, console logs, network | Debug user sessions |
| Source Maps | Map minified to original code | Readable stack traces | Production debugging |
| Breadcrumbs | Event trail before error | User actions, network, console | Error context |
| Performance | Monitor app performance | Transactions, spans, metrics | Performance tracking |
| User Context | Identify affected users | User ID, email, metadata | User-specific issues |
Example: Sentry and LogRocket integration
// Install packages
npm install @sentry/react logrocket logrocket-react
// Sentry initialization (React)
import * as Sentry from '@sentry/react';
import { BrowserTracing } from '@sentry/tracing';
Sentry.init({
dsn: 'YOUR_SENTRY_DSN',
integrations: [
new BrowserTracing(),
new Sentry.Replay({
maskAllText: false,
blockAllMedia: false,
}),
],
// Performance monitoring
tracesSampleRate: 1.0, // 100% in dev, lower in prod
// Session replay
replaysSessionSampleRate: 0.1, // 10% of sessions
replaysOnErrorSampleRate: 1.0, // 100% when error occurs
// Environment
environment: process.env.NODE_ENV,
release: process.env.REACT_APP_VERSION,
// Filter errors
beforeSend(event, hint) {
// Don't send errors in development
if (process.env.NODE_ENV === 'development') {
return null;
}
// Filter out specific errors
if (event.exception?.values?.[0]?.value?.includes('ResizeObserver')) {
return null;
}
return event;
},
});
// Wrap app with Sentry
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root'));
root.render(
<Sentry.ErrorBoundary fallback={ErrorFallback} showDialog>
<App />
</Sentry.ErrorBoundary>
);
// Set user context
Sentry.setUser({
id: user.id,
email: user.email,
username: user.name,
});
// Add breadcrumbs manually
Sentry.addBreadcrumb({
category: 'auth',
message: 'User logged in',
level: 'info',
});
// Capture exceptions manually
try {
riskyOperation();
} catch (error) {
Sentry.captureException(error);
}
// Capture messages
Sentry.captureMessage('Something went wrong', 'warning');
// Performance monitoring
const transaction = Sentry.startTransaction({ name: 'checkout' });
// Child spans
const span = transaction.startChild({
op: 'payment',
description: 'Process payment',
});
await processPayment();
span.finish();
transaction.finish();
// React component with Sentry profiling
import { withProfiler } from '@sentry/react';
function MyComponent() {
return <div>Content</div>;
}
export default withProfiler(MyComponent);
// LogRocket initialization
import LogRocket from 'logrocket';
import setupLogRocketReact from 'logrocket-react';
LogRocket.init('YOUR_LOGROCKET_APP_ID', {
release: process.env.REACT_APP_VERSION,
console: {
shouldAggregateConsoleErrors: true,
},
network: {
requestSanitizer: (request) => {
// Remove sensitive data
if (request.headers['Authorization']) {
request.headers['Authorization'] = '[REDACTED]';
}
return request;
},
},
});
// Setup LogRocket React plugin
setupLogRocketReact(LogRocket);
// Identify user
LogRocket.identify(user.id, {
name: user.name,
email: user.email,
subscriptionType: user.plan,
});
// Track custom events
LogRocket.track('Checkout Completed', {
orderId: order.id,
total: order.total,
});
// Integrate Sentry + LogRocket
import * as Sentry from '@sentry/react';
import LogRocket from 'logrocket';
LogRocket.getSessionURL((sessionURL) => {
Sentry.configureScope((scope) => {
scope.setExtra('sessionURL', sessionURL);
});
});
// Also add to error context
Sentry.init({
beforeSend(event) {
const sessionURL = LogRocket.sessionURL;
if (sessionURL) {
event.extra = event.extra || {};
event.extra.sessionURL = sessionURL;
}
return event;
},
});
// Error boundary with Sentry + LogRocket
function ErrorFallback({ error, resetErrorBoundary }) {
useEffect(() => {
// Send to Sentry
Sentry.captureException(error);
// LogRocket will automatically capture
LogRocket.captureException(error);
}, [error]);
return (
<div>
<h2>Something went wrong</h2>
<pre>{error.message}</pre>
<button onClick={resetErrorBoundary}>Try again</button>
<button onClick={() => Sentry.showReportDialog()}>
Report feedback
</button>
</div>
);
}
// Upload source maps to Sentry
// package.json
{
"scripts": {
"build": "react-scripts build && sentry-cli releases files $npm_package_version upload-sourcemaps ./build"
}
}
// .sentryclirc
[defaults]
org = your-org
project = your-project
[auth]
token = YOUR_AUTH_TOKEN
// Next.js with Sentry
// next.config.js
const { withSentryConfig } = require('@sentry/nextjs');
module.exports = withSentryConfig(
{
// Next.js config
},
{
// Sentry webpack plugin options
silent: true,
org: 'your-org',
project: 'your-project',
}
);
4. Toast Notifications react-hot-toast
| Method | Syntax | Description | Use Case |
|---|---|---|---|
| toast.success | toast.success('Saved!') |
Success notification | Successful operations |
| toast.error | toast.error('Failed!') |
Error notification | Failed operations |
| toast.loading | toast.loading('Saving...') |
Loading notification | Async operations |
| toast.promise | toast.promise(promise, msgs) |
Auto-updates based on promise | Async with feedback |
| toast.custom | toast.custom(jsx) |
Custom notification component | Custom designs |
| Position | position: 'top-right' |
Toast position on screen | UX preference |
Example: Toast notifications for user feedback
// Install react-hot-toast
npm install react-hot-toast
// Basic setup
import { Toaster, toast } from 'react-hot-toast';
function App() {
return (
<div>
<Toaster
position="top-right"
reverseOrder={false}
gutter={8}
toastOptions={{
duration: 4000,
style: {
background: '#363636',
color: '#fff',
},
success: {
duration: 3000,
iconTheme: {
primary: '#4ade80',
secondary: '#fff',
},
},
error: {
duration: 5000,
iconTheme: {
primary: '#ef4444',
secondary: '#fff',
},
},
}}
/>
<MyApp />
</div>
);
}
// Basic toast notifications
function FormExample() {
const handleSubmit = async (data) => {
try {
await saveData(data);
toast.success('Successfully saved!');
} catch (error) {
toast.error('Failed to save. Please try again.');
}
};
return <form onSubmit={handleSubmit}>...</form>;
}
// Loading toast with manual control
function AsyncOperation() {
const handleAction = async () => {
const toastId = toast.loading('Processing...');
try {
await performAsyncOperation();
toast.success('Completed!', { id: toastId });
} catch (error) {
toast.error('Failed!', { id: toastId });
}
};
return <button onClick={handleAction}>Start</button>;
}
// Promise-based toast (automatic)
function PromiseExample() {
const handleSave = () => {
const savePromise = fetch('/api/save', {
method: 'POST',
body: JSON.stringify(data),
}).then(res => res.json());
toast.promise(savePromise, {
loading: 'Saving...',
success: 'Saved successfully!',
error: 'Could not save.',
});
};
return <button onClick={handleSave}>Save</button>;
}
// Custom toast with JSX
function CustomToast() {
const notify = () => {
toast.custom((t) => (
<div
className={`${
t.visible ? 'animate-enter' : 'animate-leave'
} bg-white shadow-lg rounded-lg p-4`}
>
<div className="flex items-center">
<div className="flex-shrink-0">
<CheckIcon />
</div>
<div className="ml-3">
<h3 className="font-medium">Custom notification</h3>
<p className="text-sm text-gray-500">This is a custom toast</p>
</div>
<button onClick={() => toast.dismiss(t.id)}>
<XIcon />
</button>
</div>
</div>
));
};
return <button onClick={notify}>Show Custom Toast</button>;
}
// Toast with action button
function ToastWithAction() {
const handleDelete = () => {
toast((t) => (
<div>
<p>Are you sure you want to delete?</p>
<div className="flex gap-2 mt-2">
<button
onClick={() => {
performDelete();
toast.dismiss(t.id);
toast.success('Deleted');
}}
>
Delete
</button>
<button onClick={() => toast.dismiss(t.id)}>
Cancel
</button>
</div>
</div>
), { duration: Infinity });
};
return <button onClick={handleDelete}>Delete Item</button>;
}
// Different toast types
function ToastExamples() {
return (
<div>
<button onClick={() => toast('Basic message')}>
Default
</button>
<button onClick={() => toast.success('Success!')}>
Success
</button>
<button onClick={() => toast.error('Error!')}>
Error
</button>
<button onClick={() => toast.loading('Loading...')}>
Loading
</button>
<button onClick={() => toast('Info', { icon: 'ℹ️' })}>
Info
</button>
</div>
);
}
// Custom styling
toast.success('Styled toast', {
style: {
border: '1px solid #713200',
padding: '16px',
color: '#713200',
},
iconTheme: {
primary: '#713200',
secondary: '#FFFAEE',
},
});
// Dismiss all toasts
toast.dismiss();
// Dismiss specific toast
const toastId = toast.loading('Processing...');
toast.dismiss(toastId);
// Alternative: react-toastify
import { ToastContainer, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
function App() {
return (
<>
<ToastContainer
position="top-right"
autoClose={5000}
hideProgressBar={false}
newestOnTop
closeOnClick
rtl={false}
pauseOnFocusLoss
draggable
pauseOnHover
/>
<MyApp />
</>
);
}
toast.success('Success!');
toast.error('Error!');
toast.info('Info');
toast.warn('Warning');
5. Retry Logic Exponential Backoff
| Strategy | Formula | Description | Use Case |
|---|---|---|---|
| Exponential Backoff | delay = baseDelay * 2^attempt |
Doubles delay between retries | API rate limiting |
| Linear Backoff | delay = baseDelay * attempt |
Linear increase in delay | Simple retry scenarios |
| Jitter | delay += random(0, jitter) |
Add randomness to prevent thundering herd | Multiple clients |
| Max Retries | Limit retry attempts | Prevent infinite loops | All retry scenarios |
| Circuit Breaker | Stop retries after threshold | Fail fast when service down | Microservices |
Example: Retry logic with exponential backoff
// Basic exponential backoff
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
let lastError;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
lastError = error;
// Don't retry on last attempt
if (attempt === maxRetries) {
break;
}
// Calculate exponential backoff delay
const delay = Math.min(1000 * Math.pow(2, attempt), 10000);
console.log(`Retry ${attempt + 1}/${maxRetries} after ${delay}ms`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw lastError;
}
// Usage
try {
const data = await fetchWithRetry('/api/data');
console.log(data);
} catch (error) {
console.error('Failed after retries:', error);
}
// Advanced retry with jitter
function calculateBackoff(attempt, baseDelay = 1000, maxDelay = 30000) {
// Exponential backoff
const exponentialDelay = baseDelay * Math.pow(2, attempt);
// Add jitter (randomness)
const jitter = Math.random() * 0.3 * exponentialDelay;
// Cap at max delay
return Math.min(exponentialDelay + jitter, maxDelay);
}
async function retryWithBackoff(fn, options = {}) {
const {
maxRetries = 3,
baseDelay = 1000,
maxDelay = 30000,
shouldRetry = () => true,
onRetry = () => {},
} = options;
let lastError;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
lastError = error;
// Check if we should retry
if (attempt === maxRetries || !shouldRetry(error, attempt)) {
break;
}
// Calculate backoff delay
const delay = calculateBackoff(attempt, baseDelay, maxDelay);
// Notify retry
onRetry(error, attempt, delay);
// Wait before retry
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw lastError;
}
// Usage with custom retry conditions
const data = await retryWithBackoff(
() => fetch('/api/data').then(r => r.json()),
{
maxRetries: 5,
baseDelay: 500,
maxDelay: 10000,
shouldRetry: (error, attempt) => {
// Retry on network errors or 5xx
return error.message.includes('Failed to fetch') ||
(error.status >= 500 && error.status < 600);
},
onRetry: (error, attempt, delay) => {
console.log(`Retrying (${attempt + 1}) after ${delay}ms: ${error.message}`);
toast.error(`Request failed, retrying... (${attempt + 1})`);
},
}
);
// React hook for retry logic
function useRetry(fn, options = {}) {
const [state, setState] = useState({
data: null,
error: null,
loading: false,
retryCount: 0,
});
const execute = useCallback(async (...args) => {
setState(prev => ({ ...prev, loading: true, error: null }));
try {
const result = await retryWithBackoff(
() => fn(...args),
{
...options,
onRetry: (error, attempt) => {
setState(prev => ({ ...prev, retryCount: attempt + 1 }));
options.onRetry?.(error, attempt);
},
}
);
setState({
data: result,
error: null,
loading: false,
retryCount: 0,
});
return result;
} catch (error) {
setState(prev => ({
...prev,
error,
loading: false,
}));
throw error;
}
}, [fn, options]);
return { ...state, execute };
}
// Usage in component
function DataComponent() {
const { data, error, loading, retryCount, execute } = useRetry(
() => fetch('/api/data').then(r => r.json()),
{ maxRetries: 3 }
);
useEffect(() => {
execute();
}, []);
if (loading) return <div>Loading... {retryCount > 0 && `(Retry ${retryCount})`}</div>;
if (error) return <div>Error: {error.message} <button onClick={execute}>Retry</button></div>;
return <div>{JSON.stringify(data)}</div>;
}
// Circuit breaker pattern
class CircuitBreaker {
constructor(threshold = 5, timeout = 60000) {
this.failureThreshold = threshold;
this.resetTimeout = timeout;
this.failureCount = 0;
this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
this.nextAttempt = Date.now();
}
async execute(fn) {
if (this.state === 'OPEN') {
if (Date.now() < this.nextAttempt) {
throw new Error('Circuit breaker is OPEN');
}
this.state = 'HALF_OPEN';
}
try {
const result = await fn();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
onSuccess() {
this.failureCount = 0;
this.state = 'CLOSED';
}
onFailure() {
this.failureCount++;
if (this.failureCount >= this.failureThreshold) {
this.state = 'OPEN';
this.nextAttempt = Date.now() + this.resetTimeout;
console.log('Circuit breaker opened');
}
}
}
// Usage
const breaker = new CircuitBreaker(3, 30000);
async function callAPI() {
return breaker.execute(() => fetch('/api/data').then(r => r.json()));
}
6. Fallback UI Skeleton Loading States
| Pattern | Implementation | Description | Use Case |
|---|---|---|---|
| Skeleton Screens | Placeholder with shape | Shows content structure while loading | Better perceived performance |
| Shimmer Effect | Animated gradient | Indicates loading in progress | Visual feedback |
| Suspense | <Suspense fallback={...}> |
React's declarative loading | Code splitting, data fetching |
| Empty States | No data placeholder | Shows when no content available | Empty lists, no results |
| Error States | Error message with retry | Shows when loading fails | Failed data fetching |
| Progressive Loading | Load in stages | Load critical content first | Large pages |
Example: Skeleton screens and loading states
// Basic skeleton component
function Skeleton({ width = '100%', height = '20px', className = '' }) {
return (
<div
className={`skeleton ${className}`}
style={{
width,
height,
backgroundColor: '#e0e0e0',
borderRadius: '4px',
animation: 'pulse 1.5s ease-in-out infinite',
}}
/>
);
}
// CSS for shimmer effect
.skeleton {
background: linear-gradient(
90deg,
#e0e0e0 25%,
#f0f0f0 50%,
#e0e0e0 75%
);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
}
@keyframes shimmer {
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
}
// Card skeleton
function CardSkeleton() {
return (
<div className="card">
<Skeleton height="200px" className="mb-4" />
<Skeleton height="24px" width="80%" className="mb-2" />
<Skeleton height="16px" width="100%" className="mb-1" />
<Skeleton height="16px" width="90%" />
</div>
);
}
// List skeleton
function ListSkeleton({ count = 5 }) {
return (
<div>
{Array.from({ length: count }).map((_, i) => (
<div key={i} className="flex items-center gap-4 p-4">
<Skeleton width="50px" height="50px" style={{ borderRadius: '50%' }} />
<div className="flex-1">
<Skeleton height="16px" width="40%" className="mb-2" />
<Skeleton height="14px" width="80%" />
</div>
</div>
))}
</div>
);
}
// Data fetching with loading states
function UserProfile({ userId }) {
const [state, setState] = useState({
data: null,
loading: true,
error: null,
});
useEffect(() => {
fetchUser(userId)
.then(data => setState({ data, loading: false, error: null }))
.catch(error => setState({ data: null, loading: false, error }));
}, [userId]);
if (state.loading) return <CardSkeleton />;
if (state.error) {
return (
<div className="error-state">
<h3>Failed to load user</h3>
<p>{state.error.message}</p>
<button onClick={() => window.location.reload()}>
Try Again
</button>
</div>
);
}
if (!state.data) {
return (
<div className="empty-state">
<p>No user found</p>
</div>
);
}
return (
<div className="user-profile">
<img src={state.data.avatar} alt={state.data.name} />
<h2>{state.data.name}</h2>
<p>{state.data.bio}</p>
</div>
);
}
// React Suspense with skeleton
import { Suspense, lazy } from 'react';
const HeavyComponent = lazy(() => import('./HeavyComponent'));
function App() {
return (
<Suspense fallback={<CardSkeleton />}>
<HeavyComponent />
</Suspense>
);
}
// Custom loading hook
function useLoadingState(initialLoading = false) {
const [loading, setLoading] = useState(initialLoading);
const [error, setError] = useState(null);
const withLoading = useCallback(async (fn) => {
setLoading(true);
setError(null);
try {
const result = await fn();
return result;
} catch (err) {
setError(err);
throw err;
} finally {
setLoading(false);
}
}, []);
return { loading, error, withLoading };
}
// Usage
function DataComponent() {
const [data, setData] = useState(null);
const { loading, error, withLoading } = useLoadingState(true);
useEffect(() => {
withLoading(async () => {
const result = await fetchData();
setData(result);
});
}, []);
if (loading) return <Skeleton />;
if (error) return <ErrorState error={error} />;
return <div>{data}</div>;
}
// Skeleton library: react-loading-skeleton
import Skeleton from 'react-loading-skeleton';
import 'react-loading-skeleton/dist/skeleton.css';
function ProfileSkeleton() {
return (
<div>
<Skeleton circle width={100} height={100} />
<Skeleton count={3} />
</div>
);
}
// Progressive loading
function Dashboard() {
return (
<div>
{/* Critical content loads first */}
<Header />
{/* Non-critical wrapped in Suspense */}
<Suspense fallback={<ChartSkeleton />}>
<AnalyticsChart />
</Suspense>
<Suspense fallback={<TableSkeleton />}>
<DataTable />
</Suspense>
</div>
);
}
// Empty state component
function EmptyState({ title, description, action }) {
return (
<div className="empty-state">
<EmptyIcon />
<h3>{title}</h3>
<p>{description}</p>
{action && <button onClick={action.onClick}>{action.label}</button>}
</div>
);
}
// Usage
function ProductList({ products }) {
if (products.length === 0) {
return (
<EmptyState
title="No products found"
description="Try adjusting your filters"
action={{ label: 'Clear filters', onClick: clearFilters }}
/>
);
}
return products.map(product => <ProductCard key={product.id} {...product} />);
}
Error Handling Best Practices
- Error Boundaries - Use React Error Boundaries (getDerivedStateFromError, componentDidCatch) or react-error-boundary library for catching render errors
- Vue Error Handling - Use errorCaptured lifecycle hook in components, app.config.errorHandler for global errors, onErrorCaptured in Composition API
- Error Monitoring - Integrate Sentry for error tracking with source maps, LogRocket for session replay, add breadcrumbs and user context
- User Feedback - Use toast notifications (react-hot-toast) for success/error/loading states, show clear error messages with retry options
- Retry Logic - Implement exponential backoff with jitter, limit max retries, use circuit breaker pattern for failing services
- Loading States - Show skeleton screens during loading, empty states for no data, error states with retry, use React Suspense for declarative loading
- Production Ready - Filter sensitive errors before sending to monitoring, upload source maps for readable stack traces, set up alerts for critical errors