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
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
Feature Flags ile Entegrasyon
- Yeni özellikleri Error Boundaries ile koruyun
- A/B testleri için güvenli bir ortam oluşturun
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