React ErrorBoundary'lerini kullanarak hataları nasıl zarifçe yöneteceğinizi, uygulama çökmelerini nasıl önleyeceğinizi ve sağlam kurtarma stratejileriyle daha iyi bir kullanıcı deneyimi nasıl sunacağınızı öğrenin.
React ErrorBoundary: Hata İzolasyonu ve Kurtarma Stratejileri
Ön uç geliştirmenin dinamik dünyasında, özellikle React gibi karmaşık bileşen tabanlı çerçevelerle çalışırken beklenmedik hatalar kaçınılmazdır. Bu hatalar, doğru bir şekilde ele alınmazsa, uygulama çökmelerine ve sinir bozucu bir kullanıcı deneyimine yol açabilir. React'in ErrorBoundary bileşeni, bu hataları zarifçe ele almak, onları izole etmek ve kurtarma stratejileri sağlamak için sağlam bir çözüm sunar. Bu kapsamlı rehber, küresel bir kitle için daha dirençli ve kullanıcı dostu React uygulamaları oluşturmak üzere ErrorBoundary'nin gücünü keşfederek nasıl etkili bir şekilde uygulanacağını göstermektedir.
Hata Sınırlarını (Error Boundaries) Anlama İhtiyacı
Uygulamaya geçmeden önce, hata sınırlarının neden gerekli olduğunu anlayalım. React'te, render işlemi sırasında, yaşam döngüsü metotlarında veya alt bileşenlerin constructor'larında meydana gelen hatalar potansiyel olarak tüm uygulamanın çökmesine neden olabilir. Bunun nedeni, yakalanmamış hataların bileşen ağacında yukarı doğru yayılması ve genellikle boş bir ekrana veya yardımcı olmayan bir hata mesajına yol açmasıdır. Japonya'daki bir kullanıcının önemli bir finansal işlemi tamamlamaya çalışırken, görünüşte alakasız bir bileşendeki küçük bir hata nedeniyle boş bir ekranla karşılaştığını hayal edin. Bu durum, proaktif hata yönetimine duyulan kritik ihtiyacı göstermektedir.
Hata sınırları, alt bileşen ağaçlarının herhangi bir yerindeki JavaScript hatalarını yakalamanın, bu hataları kaydetmenin ve bileşen ağacını çökertmek yerine bir yedek kullanıcı arayüzü (fallback UI) göstermenin bir yolunu sunar. Hatalı bileşenleri izole etmenize ve uygulamanızın bir bölümündeki hataların diğerlerini etkilemesini önlemenize olanak tanıyarak küresel olarak daha istikrarlı ve güvenilir bir kullanıcı deneyimi sağlarlar.
React ErrorBoundary Nedir?
Bir ErrorBoundary, alt bileşen ağacının herhangi bir yerindeki JavaScript hatalarını yakalayan, bu hataları kaydeden ve bir yedek kullanıcı arayüzü görüntüleyen bir React bileşenidir. Aşağıdaki yaşam döngüsü metotlarından birini veya her ikisini de uygulayan bir sınıf bileşenidir:
static getDerivedStateFromError(error): Bu yaşam döngüsü metodu, bir alt bileşen tarafından bir hata fırlatıldıktan sonra çağrılır. Fırlatılan hatayı bir argüman olarak alır ve bileşenin durumunu güncellemek için bir değer döndürmelidir.componentDidCatch(error, info): Bu yaşam döngüsü metodu, bir alt bileşen tarafından bir hata fırlatıldıktan sonra çağrılır. İki argüman alır: fırlatılan hata ve hatayı hangi bileşenin fırlattığı hakkında bilgi içeren bir info nesnesi. Bu metodu hata bilgilerini kaydetmek veya diğer yan etkileri gerçekleştirmek için kullanabilirsiniz.
Temel Bir ErrorBoundary Bileşeni Oluşturma
Temel prensipleri göstermek için basit bir ErrorBoundary bileşeni oluşturalım.
Kod Örneği
İşte basit bir ErrorBoundary bileşeni için kod:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {
hasError: false,
error: null,
errorInfo: null,
};
}
static getDerivedStateFromError(error) {
// Bir sonraki render'ın yedek UI'ı göstermesi için state'i güncelle.
return {
hasError: true,
};
}
componentDidCatch(error, info) {
// Örnek "componentStack":
// in ComponentThatThrows (created by App)
// in App
console.error("Bir hata yakalandı:", error);
console.error("Hata bilgisi:", info.componentStack);
this.setState({ error: error, errorInfo: info });
// Hatayı bir hata raporlama servisine de kaydedebilirsiniz
// logErrorToMyService(error, info.componentStack);
}
render() {
if (this.state.hasError) {
// İstediğiniz özel bir yedek UI render edebilirsiniz
return (
Bir şeyler ters gitti.
Hata: {this.state.error && this.state.error.toString()}
{this.state.errorInfo && this.state.errorInfo.componentStack}
);
}
return this.props.children;
}
}
export default ErrorBoundary;
Açıklama
- Constructor: Constructor, bileşenin durumunu
hasErrorfalseolarak ayarlanmış şekilde başlatır. Ayrıca hata ayıklama amacıyla hatayı ve errorInfo'yu saklarız. getDerivedStateFromError(error): Bu statik metot, bir alt bileşen tarafından bir hata fırlatıldığında çağrılır. Durumu, bir hata oluştuğunu belirtecek şekilde günceller.componentDidCatch(error, info): Bu metot, bir hata fırlatıldıktan sonra çağrılır. Hatayı ve bileşen yığını hakkında bilgi içeren birinfonesnesini alır. Burada, hatayı konsola kaydediyoruz (tercih ettiğiniz kayıt mekanizmasıyla değiştirin, örneğin Sentry, Bugsnag veya özel bir şirket içi çözüm). Ayrıca state içinde hatayı ve errorInfo'yu ayarlıyoruz.render(): Render metodu,hasErrordurumunu kontrol eder. Eğertrueise, bir yedek kullanıcı arayüzü render eder; aksi takdirde, bileşenin alt öğelerini (children) render eder. Yedek kullanıcı arayüzü bilgilendirici ve kullanıcı dostu olmalıdır. Hata ayrıntılarını ve bileşen yığınını dahil etmek, geliştiriciler için faydalı olsa da, güvenlik nedenleriyle üretim ortamlarında koşullu olarak render edilmeli veya kaldırılmalıdır.
ErrorBoundary Bileşenini Kullanma
ErrorBoundary bileşenini kullanmak için, hata fırlatabilecek herhangi bir bileşeni onunla sarmalamanız yeterlidir.
Kod Örneği
import ErrorBoundary from './ErrorBoundary';
function MyComponent() {
return (
{/* Hata fırlatabilecek bileşenler */}
);
}
function App() {
return (
);
}
export default App;
Açıklama
Bu örnekte, MyComponent, ErrorBoundary ile sarmalanmıştır. MyComponent veya alt bileşenleri içinde herhangi bir hata meydana gelirse, ErrorBoundary bunu yakalayacak ve yedek kullanıcı arayüzünü render edecektir.
Gelişmiş ErrorBoundary Stratejileri
Temel ErrorBoundary temel düzeyde bir hata yönetimi sağlarken, hata yönetiminizi geliştirmek için uygulayabileceğiniz birkaç gelişmiş strateji vardır.
1. Parçacıklı (Granular) Hata Sınırları
Tüm uygulamayı tek bir ErrorBoundary ile sarmak yerine, parçacıklı hata sınırları kullanmayı düşünün. Bu, uygulamanızın hataya daha yatkın olan veya arızanın sınırlı bir etkiye sahip olacağı belirli bölümlerinin etrafına ErrorBoundary bileşenleri yerleştirmeyi içerir. Örneğin, bireysel widget'ları veya harici veri kaynaklarına dayanan bileşenleri sarmalayabilirsiniz.
Örnek
function ProductList() {
return (
{/* Ürün listesi */}
);
}
function RecommendationWidget() {
return (
{/* Tavsiye motoru */}
);
}
function App() {
return (
);
}
Bu örnekte, RecommendationWidget kendi ErrorBoundary'sine sahiptir. Tavsiye motoru başarısız olursa, ProductList'i etkilemez ve kullanıcı hala ürünlere göz atabilir. Bu parçacıklı yaklaşım, hataları izole ederek ve uygulama geneline yayılmasını önleyerek genel kullanıcı deneyimini iyileştirir.
2. Hata Kaydı ve Raporlama
Hataları kaydetmek, hata ayıklama ve tekrarlayan sorunları belirleme açısından çok önemlidir. componentDidCatch yaşam döngüsü metodu, Sentry, Bugsnag veya Rollbar gibi hata kayıt hizmetleriyle entegrasyon için ideal bir yerdir. Bu hizmetler, yığın izleri (stack traces), kullanıcı bağlamı ve ortam bilgileri dahil olmak üzere ayrıntılı hata raporları sunarak sorunları hızla teşhis etmenize ve çözmenize olanak tanır. GDPR gibi gizlilik düzenlemelerine uyumu sağlamak için hata günlüklerini göndermeden önce hassas kullanıcı verilerini anonimleştirmeyi veya redakte etmeyi düşünün.
Örnek
import * as Sentry from "@sentry/react";
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {
hasError: false,
};
}
static getDerivedStateFromError(error) {
// Bir sonraki render'ın yedek UI'ı göstermesi için state'i güncelle.
return {
hasError: true,
};
}
componentDidCatch(error, info) {
// Hatayı Sentry'ye kaydet
Sentry.captureException(error, { extra: info });
// Hatayı bir hata raporlama servisine de kaydedebilirsiniz
console.error("Bir hata yakalandı:", error);
}
render() {
if (this.state.hasError) {
// İstediğiniz özel bir yedek UI render edebilirsiniz
return (
Bir şeyler ters gitti.
);
}
return this.props.children;
}
}
export default ErrorBoundary;
Bu örnekte, componentDidCatch metodu, hatayı Sentry'ye bildirmek için Sentry.captureException kullanır. Sentry'yi ekibinize bildirim gönderecek şekilde yapılandırabilir, bu da kritik hatalara hızla yanıt vermenizi sağlar.
3. Özel Yedek Kullanıcı Arayüzü
ErrorBoundary tarafından görüntülenen yedek kullanıcı arayüzü, hatalar meydana geldiğinde bile kullanıcı dostu bir deneyim sunma fırsatıdır. Genel bir hata mesajı göstermek yerine, kullanıcıyı bir çözüme yönlendiren daha bilgilendirici bir mesaj görüntülemeyi düşünün. Bu, sayfayı nasıl yenileyecekleri, destekle nasıl iletişime geçecekleri veya daha sonra tekrar deneyecekleri konusunda talimatlar içerebilir. Ayrıca, meydana gelen hatanın türüne göre yedek kullanıcı arayüzünü de uyarlayabilirsiniz.
Örnek
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {
hasError: false,
error: null,
};
}
static getDerivedStateFromError(error) {
// Bir sonraki render'ın yedek UI'ı göstermesi için state'i güncelle.
return {
hasError: true,
error: error,
};
}
componentDidCatch(error, info) {
console.error("Bir hata yakalandı:", error);
// Hatayı bir hata raporlama servisine de kaydedebilirsiniz
// logErrorToMyService(error, info.componentStack);
}
render() {
if (this.state.hasError) {
// İstediğiniz özel bir yedek UI render edebilirsiniz
if (this.state.error instanceof NetworkError) {
return (
Ağ Hatası
Lütfen internet bağlantınızı kontrol edip tekrar deneyin.
);
} else {
return (
Bir şeyler ters gitti.
Lütfen sayfayı yenilemeyi deneyin veya destekle iletişime geçin.
);
}
}
return this.props.children;
}
}
export default ErrorBoundary;
Bu örnekte, yedek UI, hatanın bir NetworkError olup olmadığını kontrol eder. Eğer öyleyse, kullanıcıya internet bağlantısını kontrol etmesini söyleyen özel bir mesaj görüntüler. Aksi takdirde, genel bir hata mesajı görüntüler. Özel, eyleme geçirilebilir rehberlik sağlamak kullanıcı deneyimini büyük ölçüde iyileştirebilir.
4. Yeniden Deneme Mekanizmaları
Bazı durumlarda, hatalar geçicidir ve işlemi yeniden deneyerek çözülebilir. Başarısız olan işlemi belirli bir gecikmeden sonra otomatik olarak yeniden denemek için ErrorBoundary içinde bir yeniden deneme mekanizması uygulayabilirsiniz. Bu, ağ hatalarını veya geçici sunucu kesintilerini ele almak için özellikle yararlı olabilir. Yan etkileri olabilecek işlemler için yeniden deneme mekanizmaları uygularken dikkatli olun, çünkü bunları yeniden denemek istenmeyen sonuçlara yol açabilir.
Örnek
import React, { useState, useEffect } from 'react';
function DataFetchingComponent() {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [retryCount, setRetryCount] = useState(0);
useEffect(() => {
const fetchData = async () => {
setIsLoading(true);
try {
const response = await fetch('https://api.example.com/data');
if (!response.ok) {
throw new Error(`HTTP hatası! durum: ${response.status}`);
}
const result = await response.json();
setData(result);
setError(null);
} catch (e) {
setError(e);
setRetryCount(prevCount => prevCount + 1);
} finally {
setIsLoading(false);
}
};
if (error && retryCount < 3) {
const retryDelay = Math.pow(2, retryCount) * 1000; // Üstel geri çekilme (Exponential backoff)
console.log(`${retryDelay / 1000} saniye içinde yeniden deneniyor...`);
const timer = setTimeout(fetchData, retryDelay);
return () => clearTimeout(timer); // Bileşen kaldırıldığında veya yeniden render edildiğinde zamanlayıcıyı temizle
}
if (!data) {
fetchData();
}
}, [error, retryCount, data]);
if (isLoading) {
return Veri yükleniyor...
;
}
if (error) {
return Hata: {error.message} - {retryCount} kez yeniden denendi.
;
}
return Veri: {JSON.stringify(data)}
;
}
function App() {
return (
);
}
export default App;
Bu örnekte, DataFetchingComponent bir API'den veri almaya çalışır. Bir hata meydana gelirse, retryCount'ı artırır ve işlemi katlanarak artan bir gecikmeden sonra yeniden dener. ErrorBoundary, işlenmemiş istisnaları yakalar ve yeniden deneme denemelerinin sayısını içeren bir hata mesajı görüntüler.
5. Hata Sınırları ve Sunucu Taraflı Oluşturma (SSR)
Sunucu Taraflı Oluşturma (SSR) kullanırken, hata yönetimi daha da kritik hale gelir. Sunucu tarafı oluşturma işlemi sırasında meydana gelen hatalar tüm sunucuyu çökertebilir, bu da kesinti süresine ve kötü bir kullanıcı deneyimine yol açar. Hata sınırlarınızın hem sunucuda hem de istemcide hataları yakalayacak şekilde doğru yapılandırıldığından emin olmanız gerekir. Genellikle, Next.js ve Remix gibi SSR çerçeveleri, React Hata Sınırlarını tamamlayan kendi yerleşik hata yönetimi mekanizmalarına sahiptir.
6. Hata Sınırlarını Test Etme
Hata sınırlarını test etmek, doğru çalıştıklarından ve beklenen yedek kullanıcı arayüzünü sağladıklarından emin olmak için esastır. Hata koşullarını simüle etmek ve hata sınırlarınızın hataları yakaladığını ve uygun yedek kullanıcı arayüzünü render ettiğini doğrulamak için Jest ve React Testing Library gibi test kütüphanelerini kullanın. Hata sınırlarınızın sağlam olduğundan ve çok çeşitli senaryoları ele aldığından emin olmak için farklı hata türlerini ve uç durumları test etmeyi düşünün.
Örnek
import { render, screen } from '@testing-library/react';
import ErrorBoundary from './ErrorBoundary';
function ComponentThatThrows() {
throw new Error('Bu bileşen bir hata fırlatıyor');
return Bunun render edilmemesi gerekiyor
;
}
test('bir hata fırlatıldığında yedek UI'ı render eder', () => {
render(
);
const errorMessage = screen.getByText(/Bir şeyler ters gitti/i);
expect(errorMessage).toBeInTheDocument();
});
Bu test, bir ErrorBoundary içinde bir hata fırlatan bir bileşeni render eder. Ardından, hata mesajının belgede mevcut olup olmadığını kontrol ederek yedek kullanıcı arayüzünün doğru şekilde render edilip edilmediğini doğrular.
7. Zarif Düşüş (Graceful Degradation)
Hata sınırları, React uygulamalarınızda zarif düşüş uygulamanın önemli bir bileşenidir. Zarif düşüş, uygulamanızı, bazı bölümleri başarısız olduğunda bile azaltılmış işlevsellikle de olsa çalışmaya devam edecek şekilde tasarlama pratiğidir. Hata sınırları, başarısız olan bileşenleri izole etmenize ve uygulamanın geri kalanını etkilemelerini önlemenize olanak tanır. Bir yedek kullanıcı arayüzü ve alternatif işlevsellik sağlayarak, hatalar meydana geldiğinde bile kullanıcıların temel özelliklere erişebilmesini sağlayabilirsiniz.
Kaçınılması Gereken Yaygın Hatalar
ErrorBoundary güçlü bir araç olsa da, kaçınılması gereken bazı yaygın tuzaklar vardır:
- Asenkron kodu sarmamak:
ErrorBoundaryyalnızca render sırasında, yaşam döngüsü metotlarında ve constructor'larda hataları yakalar. Asenkron koddaki (ör.setTimeout,Promise'ler) hatalarıntry...catchblokları kullanılarak yakalanması ve asenkron fonksiyon içinde uygun şekilde ele alınması gerekir. - Hata Sınırlarını Aşırı Kullanmak: Uygulamanızın büyük bölümlerini tek bir
ErrorBoundaryile sarmaktan kaçının. Bu, hataların kaynağını izole etmeyi zorlaştırabilir ve çok sık genel bir yedek kullanıcı arayüzünün görüntülenmesine yol açabilir. Belirli bileşenleri veya özellikleri izole etmek için parçacıklı hata sınırları kullanın. - Hata Bilgilerini Göz Ardı Etmek: Sadece hataları yakalayıp bir yedek kullanıcı arayüzü göstermeyin. Hata bilgilerini (bileşen yığını dahil) bir hata raporlama hizmetine veya konsolunuza kaydettiğinizden emin olun. Bu, altta yatan sorunları teşhis etmenize ve düzeltmenize yardımcı olacaktır.
- Üretimde Hassas Bilgileri Görüntülemek: Üretim ortamlarında ayrıntılı hata bilgilerini (ör. yığın izleri) görüntülemekten kaçının. Bu, hassas bilgileri kullanıcılara ifşa edebilir ve bir güvenlik riski olabilir. Bunun yerine, kullanıcı dostu bir hata mesajı görüntüleyin ve ayrıntılı bilgileri bir hata raporlama hizmetine kaydedin.
Fonksiyonel Bileşenler ve Hook'lar ile Hata Sınırları
Hata Sınırları sınıf bileşenleri olarak uygulansa da, hook kullanan fonksiyonel bileşenler içindeki hataları etkili bir şekilde yönetmek için bunları kullanabilirsiniz. Tipik yaklaşım, daha önce gösterildiği gibi, fonksiyonel bileşeni bir ErrorBoundary bileşeni içinde sarmalamayı içerir. Hata yönetimi mantığı, ErrorBoundary içinde bulunur ve fonksiyonel bileşenin render edilmesi veya hook'ların yürütülmesi sırasında meydana gelebilecek hataları etkili bir şekilde izole eder.
Özellikle, fonksiyonel bileşenin render edilmesi sırasında veya bir useEffect hook'unun gövdesi içinde fırlatılan herhangi bir hata ErrorBoundary tarafından yakalanacaktır. Ancak, ErrorBoundary'lerin fonksiyonel bileşen içindeki DOM öğelerine eklenmiş olay işleyicileri (ör. onClick, onChange) içinde meydana gelen hataları yakalamadığını belirtmek önemlidir. Olay işleyicileri için, hata yönetimi için geleneksel try...catch bloklarını kullanmaya devam etmelisiniz.
Hata Mesajlarının Uluslararasılaştırılması ve Yerelleştirilmesi
Küresel bir kitle için uygulamalar geliştirirken, hata mesajlarınızı uluslararasılaştırmak ve yerelleştirmek çok önemlidir. ErrorBoundary'nin yedek kullanıcı arayüzünde görüntülenen hata mesajları, daha iyi bir kullanıcı deneyimi sağlamak için kullanıcının tercih ettiği dile çevrilmelidir. Çevirilerinizi yönetmek ve kullanıcının yerel ayarına göre uygun hata mesajını dinamik olarak görüntülemek için i18next veya React Intl gibi kütüphaneleri kullanabilirsiniz.
i18next kullanarak örnek
import i18next from 'i18next';
import { useTranslation } from 'react-i18next';
i18next.init({
resources: {
en: {
translation: {
'error.generic': 'Something went wrong. Please try again later.',
'error.network': 'Network error. Please check your internet connection.',
},
},
tr: {
translation: {
'error.generic': 'Bir şeyler ters gitti. Lütfen daha sonra tekrar deneyin.',
'error.network': 'Ağ hatası. Lütfen internet bağlantınızı kontrol edin.',
},
},
},
lng: 'tr',
fallbackLng: 'en',
interpolation: {
escapeValue: false, // react için gerekli değil çünkü varsayılan olarak escape ediyor
},
});
function ErrorFallback({ error }) {
const { t } = useTranslation();
let errorMessageKey = 'error.generic';
if (error instanceof NetworkError) {
errorMessageKey = 'error.network';
}
return (
{t('error.generic')}
{t(errorMessageKey)}
);
}
function ErrorBoundary({ children }) {
const [hasError, setHasError] = useState(false);
const [error, setError] = useState(null);
// Bu kısım doğrudan hook'larla bu şekilde çalışmaz, bir sınıf bileşeni gerektirir.
// Bu örnek, uluslararasılaştırma konseptini göstermek içindir.
// Gerçek uygulamada, bu bir sınıf bileşeni olurdu veya react-error-boundary gibi bir kütüphane kullanılırdı.
static getDerivedStateFromError = (error) => {
// Bir sonraki render'ın yedek UI'ı göstermesi için state'i güncelle
setHasError(true);
setError(error);
}
if (hasError) {
// İstediğiniz özel bir yedek UI render edebilirsiniz
return ;
}
return children;
}
export default ErrorBoundary;
Bu örnekte, İngilizce ve Türkçe çevirileri yönetmek için i18next kullanıyoruz. ErrorFallback bileşeni, mevcut dile göre uygun hata mesajını almak için useTranslation hook'unu kullanır. Bu, kullanıcıların hata mesajlarını tercih ettikleri dilde görmelerini sağlayarak genel kullanıcı deneyimini artırır.
Sonuç
React ErrorBoundary bileşenleri, sağlam ve kullanıcı dostu React uygulamaları oluşturmak için çok önemli bir araçtır. Hata sınırlarını uygulayarak, hataları zarifçe yönetebilir, uygulama çökmelerini önleyebilir ve dünya çapındaki kullanıcılar için daha iyi bir kullanıcı deneyimi sağlayabilirsiniz. Hata sınırlarının prensiplerini anlayarak, parçacıklı hata sınırları, hata kaydı ve özel yedek kullanıcı arayüzleri gibi gelişmiş stratejiler uygulayarak ve yaygın tuzaklardan kaçınarak, küresel bir kitlenin ihtiyaçlarını karşılayan daha dirençli ve güvenilir React uygulamaları oluşturabilirsiniz. Gerçekten kapsayıcı bir kullanıcı deneyimi sağlamak için hata mesajlarını görüntülerken uluslararasılaştırmayı ve yerelleştirmeyi göz önünde bulundurmayı unutmayın. Web uygulamalarının karmaşıklığı artmaya devam ettikçe, hata yönetimi tekniklerinde ustalaşmak, yüksek kaliteli yazılım oluşturan geliştiriciler için giderek daha önemli hale gelecektir.