React'ta Modern Error Boundaries ve Hata Yönetimi

Yunus Emre Güzel
7 Ocak 202515 dkReact
React'ta Modern Error Boundaries ve Hata Yönetimi

React'ta Modern Error Boundaries ve Hata Yönetimi

Modern web uygulamalarında hata yönetimi, kullanıcı deneyiminin kritik bir parçasıdır. React 18 ile birlikte, hata yönetimi yaklaşımları da önemli ölçüde gelişti. Bu yazıda, React'ın Error Boundaries özelliğini derinlemesine inceleyeceğiz ve modern uygulamalarda nasıl etkili bir şekilde kullanabileceğimizi öğreneceğiz.

Error Boundaries Nedir ve Neden İhtiyaç Duyarız?

Error Boundaries, React uygulamalarında oluşabilecek JavaScript hatalarını yakalayan ve bu hataları zarif bir şekilde işleyen özel React bileşenleridir. Bu bileşenler sayesinde:

  • Uygulama tamamen çökmek yerine kontrollü bir şekilde hata durumunu yönetebilir
  • Kullanıcılara anlamlı hata mesajları gösterebilir
  • Hataları loglayabilir ve izleyebilir
  • Uygulamanın belirli bölümlerini izole edebilir

Örneğin, bir e-ticaret uygulamasında ürün listesi yüklenirken bir hata oluştuğunda, tüm uygulama çökmek yerine sadece o bölümde bir hata mesajı gösterebilir ve kullanıcının diğer sayfalarda gezinmeye devam etmesine izin verebilir.

Modern Error Boundary Implementasyonu

React 18'de Error Boundary'ler hala class component olarak yazılmak zorunda. Ancak modern TypeScript özellikleri ve hook'lar ile çok daha güçlü bir implementasyon yapabiliriz. İşte modern bir Error Boundary implementasyonu:

import {
  Component,
  ErrorInfo,
  ReactNode,
  isValidElement,
  createElement,
  Fragment
} from 'react';

interface FallbackProps {
  error: Error;
  resetError: () => void;
}

type FallbackElement = ReactNode | ((props: FallbackProps) => ReactNode);

interface Props {
  children: ReactNode;
  fallback?: FallbackElement;
  onError?: (error: Error, errorInfo: ErrorInfo) => void;
  onReset?: () => void;
  resetKeys?: unknown[];
}

interface State {
  error: Error | null;
  previousResetKeys?: unknown[];
}

class ErrorBoundary extends Component<Props, State> {
  constructor(props: Props) {
    super(props);
    this.state = { error: null };
  }

  static getDerivedStateFromError(error: Error): State {
    return { error };
  }

  static getDerivedStateFromProps(props: Props, state: State): State | null {
    // resetKeys değiştiğinde error state'ini sıfırla
    if (state.error && props.resetKeys) {
      if (!state.previousResetKeys) {
        return {
          error: null,
          previousResetKeys: props.resetKeys
        };
      }

      if (!areArraysEqual(state.previousResetKeys, props.resetKeys)) {
        return {
          error: null,
          previousResetKeys: props.resetKeys
        };
      }
    }

    return null;
  }

  componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
    this.props.onError?.(error, errorInfo);
  }

  resetError = () => {
    this.props.onReset?.();
    this.setState({ error: null });
  };

  render() {
    const { error } = this.state;
    const { children, fallback } = this.props;

    if (error) {
      const fallbackProps: FallbackProps = {
        error,
        resetError: this.resetError
      };

      // Fallback bir fonksiyon ise
      if (typeof fallback === 'function') {
        return fallback(fallbackProps);
      }

      // Fallback bir element ise
      if (isValidElement(fallback)) {
        return fallback;
      }

      // Default fallback
      return createElement(DefaultFallback, fallbackProps);
    }

    return children;
  }
}

// Yardımcı fonksiyonlar
function areArraysEqual(a: unknown[], b: unknown[]): boolean {
  return (
    a.length === b.length &&
    a.every((item, index) => Object.is(item, b[index]))
  );
}

// Modern, erişilebilir default fallback component
function DefaultFallback({ error, resetError }: FallbackProps) {
  return (
    <div
      role="alert"
      className="p-6 rounded-xl bg-red-50 dark:bg-red-900/30 shadow-lg"
    >
      <div className="flex items-center gap-3">
        <svg
          className="w-6 h-6 text-red-600 dark:text-red-400 shrink-0"
          fill="none"
          stroke="currentColor"
          viewBox="0 0 24 24"
          aria-hidden="true"
        >
          <path
            strokeLinecap="round"
            strokeLinejoin="round"
            strokeWidth={2}
            d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
          />
        </svg>
        <div>
          <h2 className="text-lg font-semibold text-red-800 dark:text-red-200">
            Bir Hata Oluştu
          </h2>
          <p className="mt-1 text-sm text-red-700 dark:text-red-300">
            {error.message}
          </p>
        </div>
      </div>
      <div className="mt-6 flex gap-3">
        <button
          onClick={resetError}
          className="px-4 py-2 text-sm font-medium text-white bg-red-600 hover:bg-red-700 rounded-lg transition focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800"
        >
          Yeniden Dene
        </button>
        <button
          onClick={() => window.location.reload()}
          className="px-4 py-2 text-sm font-medium text-red-700 bg-red-100 hover:bg-red-200 rounded-lg transition focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 dark:text-red-200 dark:bg-red-900/50 dark:hover:bg-red-900/70"
        >
          Sayfayı Yenile
        </button>
      </div>
    </div>
  );
}

// Modern hook kullanımı
function useErrorBoundary() {
  const [key, setKey] = useState(0);
  const resetErrorBoundary = useCallback(() => {
    setKey(k => k + 1);
  }, []);

  return {
    key,
    resetErrorBoundary
  };
}

export { ErrorBoundary, useErrorBoundary };
export type { FallbackProps };

Error Boundaries'i Etkili Kullanma Stratejileri

1. Granüler Hata Yönetimi

Error Boundaries'i uygulamanızda stratejik noktalara yerleştirmek önemlidir. Her şeyi tek bir Error Boundary ile sarmak yerine, mantıksal bölümlere ayırarak kullanmak daha etkilidir:

function Dashboard() {
  return (
    <div className="grid grid-cols-12 gap-4">
      {/* Her widget kendi Error Boundary'sine sahip */}
      <div className="col-span-4">
        <ErrorBoundary>
          <AnalyticsWidget />
        </ErrorBoundary>
      </div>
      <div className="col-span-4">
        <ErrorBoundary>
          <RevenueWidget />
        </ErrorBoundary>
      </div>
    </div>
  );
}

Bu yaklaşımın avantajları:

  • Bir widget'ın hatası diğerlerini etkilemez
  • Daha iyi hata izolasyonu sağlar
  • Kullanıcı deneyimi daha az etkilenir

2. Suspense ile Entegrasyon

React 18'in Suspense özelliği ile Error Boundaries'i birlikte kullanmak, asenkron işlemleri daha iyi yönetmemizi sağlar:

function AsyncComponent() {
  return (
    <ErrorBoundary
      fallback={({ error, resetError }) => (
        <ErrorFallback error={error} resetError={resetError} />
      )}
    >
      <Suspense fallback={<LoadingSpinner />}>
        <MyAsyncComponent />
      </Suspense>
    </ErrorBoundary>
  );
}

Bu pattern şu durumlarda özellikle faydalıdır:

  • Veri fetching işlemlerinde
  • Code splitting ve lazy loading senaryolarında
  • Ağır hesaplama gerektiren işlemlerde

3. Custom Hook'lar ile Zenginleştirme

Error Boundaries'i custom hook'lar ile birleştirerek daha zengin hata yönetimi çözümleri oluşturabiliriz:

function useAsyncOperation<T>(
  operation: () => Promise<T>,
  options: UseAsyncOperationOptions<T> = {}
) {
  // ... hook implementation
}

Bu hook'un sağladığı avantajlar:

  • Daha temiz ve yeniden kullanılabilir kod
  • Tutarlı hata yönetimi
  • Daha iyi tip güvenliği

Modern Error Tracking ve Monitoring

Performance Monitoring

Modern uygulamalarda hata izleme ve performans monitoring kritik öneme sahiptir. Sentry gibi araçlarla entegrasyon yaparak:

import * as Sentry from '@sentry/react';

Sentry.init({
  dsn: 'YOUR_DSN',
  integrations: [
    new BrowserTracing({
      tracePropagationTargets: ['localhost', 'your-domain.com']
    })
  ],
  // ... configuration
});

Bu entegrasyonun faydaları:

  • Gerçek zamanlı hata izleme
  • Performans metriklerini toplama
  • Kullanıcı deneyimi sorunlarını tespit etme

Analytics ve Logging

Hataları sadece yakalamak değil, analiz etmek de önemlidir:

function trackError(error: Error, errorInfo: ErrorInfo) {
  // ... error tracking implementation
}

Bu yaklaşımın sağladığı insights:

  • Hangi hataların sık oluştuğu
  • Hataların hangi kullanıcı segmentlerinde yoğunlaştığı
  • Hata patterns ve trendleri

Test Etme ve Kalite Güvencesi

Error Boundaries'i test etmek, güvenilir bir hata yönetimi stratejisi için kritiktir:

describe('ErrorBoundary', () => {
  // ... test implementation
});

Test stratejinizde şunlara dikkat edin:

  • Happy path senaryoları
  • Hata senaryoları
  • Async error handling
  • Reset functionality
  • Edge cases

Best Practices ve Öneriler

  1. Mikro-frontend'lerde Kullanım

    • Her mikro-frontend'i kendi Error Boundary'si ile sarın
    • Cross-cutting concerns için üst seviye Error Boundaries kullanın
  2. Feature Flags ile Entegrasyon

    • Yeni özellikleri Error Boundaries ile koruyun
    • A/B testleri için güvenli bir ortam oluşturun
  3. Performans Optimizasyonu

    • Gereksiz re-render'ları önleyin
    • Memoization tekniklerini kullanın
    • Code splitting ile bundle size'ı optimize edin

Sonuç

Modern React uygulamalarında Error Boundaries, artık sadece basit bir hata yakalama mekanizması değil, kapsamlı bir hata yönetimi stratejisinin temel taşıdır. TypeScript 5 ve React 18'in modern özellikleriyle birlikte kullanıldığında:

  • Daha güvenilir uygulamalar
  • Daha iyi kullanıcı deneyimi
  • Daha kolay maintenance
  • Daha iyi monitoring ve debugging

sağlar.

Bu yaklaşımları kendi projelerinizde uygulayarak, hem kullanıcılarınız hem de geliştirici ekibiniz için daha sağlam ve sürdürülebilir uygulamalar geliştirebilirsiniz.

İlgili Etiketler: #React18 #ErrorBoundaries #TypeScript5 #Suspense #Testing #Performance #ModernReact

Kaynaklar