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