React'te sağlam hata yönetimi ve zarif kullanıcı arayüzü bozulması için JavaScript Hata Sınırlarını anlama ve uygulama üzerine kapsamlı bir rehber.
JavaScript Hata Sınırı (Error Boundary): Bir React Hata Yönetimi Uygulama Rehberi
React geliştirme dünyasında, beklenmedik hatalar sinir bozucu kullanıcı deneyimlerine ve uygulama kararsızlığına yol açabilir. Sağlam ve güvenilir uygulamalar oluşturmak için iyi tanımlanmış bir hata yönetimi stratejisi çok önemlidir. React'in Hata Sınırları (Error Boundaries), bileşen ağacınızda meydana gelen hataları zarif bir şekilde yönetmek, tüm uygulamanın çökmesini önlemek ve bir yedek arayüz (fallback UI) göstermenize olanak tanımak için güçlü bir mekanizma sağlar.
Hata Sınırı (Error Boundary) Nedir?
Bir Hata Sınırı, alt bileşen ağacının herhangi bir yerindeki JavaScript hatalarını yakalayan, bu hataları kaydeden ve çöken bileşen ağacı yerine bir yedek arayüz gösteren bir React bileşenidir. Hata Sınırları, render sırasında, yaşam döngüsü metotlarında ve altlarındaki tüm ağacın yapıcı metotlarında (constructors) meydana gelen hataları yakalar.
Bir Hata Sınırını, React bileşenleri için bir try...catch
bloğu gibi düşünebilirsiniz. Tıpkı bir try...catch
bloğunun senkron JavaScript kodundaki istisnaları yönetmenize olanak tanıdığı gibi, bir Hata Sınırı da React bileşenlerinizin render edilmesi sırasında meydana gelen hataları yönetmenize olanak tanır.
Önemli Not: Hata Sınırları aşağıdaki durumlar için hataları yakalamaz:
- Olay yöneticileri (event handlers) (daha fazlasını sonraki bölümlerde öğrenin)
- Asenkron kod (ör.
setTimeout
veyarequestAnimationFrame
geri aramaları) - Sunucu taraflı oluşturma (server-side rendering)
- Hata Sınırının kendisinde (alt bileşenlerinde değil) oluşan hatalar
Neden Hata Sınırları Kullanmalısınız?
Hata Sınırları kullanmak birçok önemli avantaj sunar:
- Geliştirilmiş Kullanıcı Deneyimi: Boş beyaz bir ekran veya anlaşılmaz bir hata mesajı göstermek yerine, kullanıcıya bir şeylerin yanlış gittiğini bildiren ve potansiyel olarak kurtarma yolu sunan (ör. sayfayı yeniden yükleme veya farklı bir bölüme gitme) kullanıcı dostu bir yedek arayüz gösterebilirsiniz.
- Uygulama Kararlılığı: Hata Sınırları, uygulamanızın bir bölümündeki hataların tüm uygulamanın çökmesini engeller. Bu, özellikle birçok birbiriyle bağlantılı bileşene sahip karmaşık uygulamalar için önemlidir.
- Merkezi Hata Yönetimi: Hata Sınırları, hataları kaydetmek ve sorunların temel nedenini bulmak için merkezi bir konum sağlar. Bu, hata ayıklama ve bakımı basitleştirir.
- Zarif Bozulma (Graceful Degradation): Uygulamanızın farklı bölümlerine stratejik olarak Hata Sınırları yerleştirerek, bazı bileşenler başarısız olsa bile uygulamanın geri kalanının işlevsel kalmasını sağlayabilirsiniz. Bu, hatalar karşısında zarif bir şekilde bozulmaya olanak tanır.
React'te Hata Sınırlarını Uygulama
Bir Hata Sınırı oluşturmak için, aşağıdaki yaşam döngüsü yöntemlerinden birini (veya her ikisini) uygulayan bir sınıf bileşeni (class component) tanımlamanız gerekir:
static getDerivedStateFromError(error)
: Bu yaşam döngüsü yöntemi, 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 (state) bir hatanın meydana geldiğini gösterecek şekilde güncellemek için bir değer döndürmelidir (ör. birhasError
bayrağınıtrue
olarak ayarlamak).componentDidCatch(error, info)
: Bu yaşam döngüsü yöntemi, 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 ve hatayı hangi bileşenin fırlattığı hakkında bilgi içeren birinfo
nesnesiyle birlikte alır. Bu yöntemi, hatayı Sentry veya Bugsnag gibi bir servise kaydetmek için kullanabilirsiniz.
İşte temel bir Hata Sınırı bileşeni örneği:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {
hasError: false,
error: null,
errorInfo: null
};
}
static getDerivedStateFromError(error) {
// Durumu (state) güncelleyin, böylece bir sonraki render yedek arayüzü gösterecektir.
return {
hasError: true,
error: error
};
}
componentDidCatch(error, info) {
// Örnek "componentStack":
// in ComponentThatThrows (created by App)
// in MyErrorBoundary (created by App)
// in div (created by App)
// in App
console.error("Bir hata yakalandı:", error, info);
this.setState({
errorInfo: info.componentStack
});
// Hatayı bir hata raporlama servisine de kaydedebilirsiniz
//logErrorToMyService(error, info.componentStack);
}
render() {
if (this.state.hasError) {
// İstediğiniz özel bir yedek arayüzü render edebilirsiniz
return (
<div>
<h2>Bir şeyler ters gitti.</h2>
<p>Hata: {this.state.error ? this.state.error.message : "Bilinmeyen bir hata oluştu."}</p>
<details style={{ whiteSpace: 'pre-wrap' }}>
{this.state.errorInfo && this.state.errorInfo}
</details>
</div>
);
}
return this.props.children;
}
}
Hata Sınırını kullanmak için, korumak istediğiniz bileşen ağacını basitçe sarmalayın:
<ErrorBoundary>
<MyComponentThatMightThrow/>
</ErrorBoundary>
Hata Sınırı Kullanımının Pratik Örnekleri
Hata Sınırlarının özellikle yararlı olabileceği bazı pratik senaryoları inceleyelim:
1. API Hatalarını Yönetme
Bir API'den veri çekerken, ağ sorunları, sunucu problemleri veya geçersiz veri nedeniyle hatalar oluşabilir. Veriyi çeken ve gösteren bileşeni bir Hata Sınırı ile sarmalayarak bu hataları zarif bir şekilde yönetebilirsiniz.
function UserProfile() {
const [user, setUser] = React.useState(null);
const [isLoading, setIsLoading] = React.useState(true);
React.useEffect(() => {
async function fetchData() {
try {
const response = await fetch('/api/user');
if (!response.ok) {
throw new Error(`HTTP hatası! durum: ${response.status}`);
}
const data = await response.json();
setUser(data);
} catch (error) {
// Hata, ErrorBoundary tarafından yakalanacaktır
throw error;
} finally {
setIsLoading(false);
}
}
fetchData();
}, []);
if (isLoading) {
return <p>Kullanıcı profili yükleniyor...</p>;
}
if (!user) {
return <p>Kullanıcı verisi mevcut değil.</p>;
}
return (
<div>
<h2>{user.name}</h2>
<p>E-posta: {user.email}</p>
</div>
);
}
function App() {
return (
<ErrorBoundary>
<UserProfile />
</ErrorBoundary>
);
}
Bu örnekte, API çağrısı başarısız olursa veya bir hata döndürürse, Hata Sınırı hatayı yakalayacak ve (Hata Sınırının render
metodu içinde tanımlanan) bir yedek arayüz gösterecektir. Bu, tüm uygulamanın çökmesini engeller ve kullanıcıya daha bilgilendirici bir mesaj sunar. Yedek arayüzü, isteği yeniden deneme seçeneği sunacak şekilde genişletebilirsiniz.
2. Üçüncü Taraf Kütüphane Hatalarını Yönetme
Üçüncü taraf kütüphaneleri kullanırken, beklenmedik hatalar fırlatmaları mümkündür. Bu kütüphaneleri kullanan bileşenleri Hata Sınırları ile sarmalamak, bu hataları zarif bir şekilde yönetmenize yardımcı olabilir.
Veri tutarsızlıkları veya diğer sorunlar nedeniyle zaman zaman hata fırlatan varsayımsal bir grafik kütüphanesi düşünün. Grafik bileşenini şu şekilde sarmalayabilirsiniz:
function MyChartComponent() {
try {
// Grafiği üçüncü taraf kütüphanesini kullanarak render et
return <Chart data={data} />;
} catch (error) {
// Bu catch bloğu, React bileşeni yaşam döngüsü hataları için etkili olmayacaktır
// Esas olarak bu belirli fonksiyon içindeki senkron hatalar içindir.
console.error("Grafik render edilirken hata oluştu:", error);
// Hatanın ErrorBoundary tarafından yakalanması için fırlatmayı düşünün
throw error; // Hatayı yeniden fırlatma
}
}
function App() {
return (
<ErrorBoundary>
<MyChartComponent />
</ErrorBoundary>
);
}
Eğer Chart
bileşeni bir hata fırlatırsa, Hata Sınırı onu yakalayacak ve bir yedek arayüz gösterecektir. MyChartComponent içindeki try/catch'in yalnızca senkron fonksiyon içindeki hataları yakalayacağını, bileşenin yaşam döngüsünü yakalamayacağını unutmayın. Bu nedenle, ErrorBoundary burada kritik öneme sahiptir.
3. Oluşturma (Rendering) Hatalarını Yönetme
Oluşturma işlemi sırasında geçersiz veri, yanlış prop türleri veya diğer sorunlar nedeniyle hatalar oluşabilir. Hata Sınırları bu hataları yakalayabilir ve uygulamanın çökmesini önleyebilir.
function DisplayName({ name }) {
if (typeof name !== 'string') {
throw new Error('İsim bir metin (string) olmalıdır');
}
return <h2>Merhaba, {name}!</h2>;
}
function App() {
return (
<ErrorBoundary>
<DisplayName name={123} /> <!-- Yanlış prop türü -->
</ErrorBoundary>
);
}
Bu örnekte, DisplayName
bileşeni name
prop'unun bir string olmasını bekler. Bunun yerine bir sayı geçirilirse, bir hata fırlatılacak ve Hata Sınırı bunu yakalayıp bir yedek arayüz gösterecektir.
Hata Sınırları ve Olay Yöneticileri (Event Handlers)
Daha önce belirtildiği gibi, Hata Sınırları olay yöneticileri içinde meydana gelen hataları yakalamaz. Bunun nedeni, olay yöneticilerinin genellikle asenkron olması ve Hata Sınırlarının yalnızca oluşturma sırasında, yaşam döngüsü yöntemlerinde ve yapıcılarda (constructors) meydana gelen hataları yakalamasıdır.
Olay yöneticilerindeki hataları yönetmek için, olay yönetici fonksiyonu içinde geleneksel bir try...catch
bloğu kullanmanız gerekir.
function MyComponent() {
const handleClick = () => {
try {
// Hata fırlatabilecek bir kod
throw new Error('Olay yöneticisinde bir hata oluştu');
} catch (error) {
console.error('Olay yöneticisinde bir hata yakalandı:', error);
// Hatayı yönet (ör. kullanıcıya bir hata mesajı göster)
}
};
return <button onClick={handleClick}>Bana Tıkla</button>;
}
Global Hata Yönetimi
Hata Sınırları, React bileşen ağacındaki hataları yönetmek için mükemmel olsa da, tüm olası hata senaryolarını kapsamazlar. Örneğin, React bileşenleri dışında meydana gelen hataları, global olay dinleyicilerindeki hatalar veya React başlatılmadan önce çalışan koddaki hatalar gibi durumları yakalamazlar.
Bu tür hataları yönetmek için window.onerror
olay yöneticisini kullanabilirsiniz.
window.onerror = function(message, source, lineno, colno, error) {
console.error('Global hata yöneticisi:', message, source, lineno, colno, error);
// Hatayı Sentry veya Bugsnag gibi bir servise kaydet
// Kullanıcıya global bir hata mesajı göster (isteğe bağlı)
return true; // Varsayılan hata yönetimi davranışını engelle
};
window.onerror
olay yöneticisi, yakalanmamış bir JavaScript hatası meydana geldiğinde çağrılır. Bunu hatayı kaydetmek, kullanıcıya global bir hata mesajı göstermek veya hatayı yönetmek için başka eylemler gerçekleştirmek amacıyla kullanabilirsiniz.
Önemli: window.onerror
olay yöneticisinden true
döndürmek, tarayıcının varsayılan hata mesajını göstermesini engeller. Ancak, kullanıcı deneyimini göz önünde bulundurun; varsayılan mesajı bastırırsanız, açık ve bilgilendirici bir alternatif sunduğunuzdan emin olun.
Hata Sınırlarını Kullanmak için En İyi Uygulamalar
Hata Sınırlarını kullanırken akılda tutulması gereken bazı en iyi uygulamalar şunlardır:
- Hata Sınırlarını stratejik olarak yerleştirin: Hataları izole etmek ve yayılmalarını önlemek için uygulamanızın farklı bölümlerini Hata Sınırları ile sarmalayın. Tüm rotaları (routes) veya arayüzünüzün ana bölümlerini sarmalamayı düşünün.
- Bilgilendirici yedek arayüz sağlayın: Yedek arayüz, kullanıcıya bir hata oluştuğunu bildirmeli ve potansiyel olarak bir kurtarma yolu sunmalıdır. "Bir şeyler yanlış gitti" gibi genel hata mesajları göstermekten kaçının.
- Hataları kaydedin: Hataları Sentry veya Bugsnag gibi bir servise kaydetmek için
componentDidCatch
yaşam döngüsü yöntemini kullanın. Bu, sorunların temel nedenini bulmanıza ve uygulamanızın kararlılığını artırmanıza yardımcı olacaktır. - Beklenen hatalar için Hata Sınırları kullanmayın: Hata Sınırları, beklenmedik hataları yönetmek için tasarlanmıştır. Beklenen hatalar (ör. doğrulama hataları, API hataları) için
try...catch
blokları veya özel hata yönetimi bileşenleri gibi daha spesifik hata yönetimi mekanizmaları kullanın. - Birden çok seviyede Hata Sınırı düşünün: Farklı hata yönetimi seviyeleri sağlamak için Hata Sınırlarını iç içe kullanabilirsiniz. Örneğin, yakalanmamış hataları yakalayan ve genel bir hata mesajı gösteren global bir Hata Sınırınız ve belirli bileşenlerdeki hataları yakalayıp daha ayrıntılı hata mesajları gösteren daha spesifik Hata Sınırlarınız olabilir.
- Sunucu taraflı oluşturmayı unutmayın: Sunucu taraflı oluşturma (SSR) kullanıyorsanız, sunucudaki hataları da yönetmeniz gerekir. Hata Sınırları sunucuda çalışır, ancak ilk render sırasında meydana gelen hataları yakalamak için ek hata yönetimi mekanizmaları kullanmanız gerekebilir.
Gelişmiş Hata Sınırı Teknikleri
1. Render Prop Kullanımı
Statik bir yedek arayüz render etmek yerine, hataların nasıl yönetileceği konusunda daha fazla esneklik sağlamak için bir render prop kullanabilirsiniz. Render prop, bir bileşenin bir şeyi render etmek için kullandığı bir fonksiyon prop'udur.
class ErrorBoundary extends React.Component {
// ... (önceki gibi)
render() {
if (this.state.hasError) {
// Yedek arayüzü render etmek için render prop'u kullan
return this.props.fallbackRender(this.state.error, this.state.errorInfo);
}
return this.props.children;
}
}
function App() {
return (
<ErrorBoundary fallbackRender={(error, errorInfo) => (
<div>
<h2>Bir şeyler ters gitti!</h2>
<p>Hata: {error.message}</p>
<details style={{ whiteSpace: 'pre-wrap' }}>
{errorInfo.componentStack}
</details>
</div>
)}>
<MyComponentThatMightThrow/>
</ErrorBoundary>
);
}
Bu, yedek arayüzü her bir Hata Sınırı bazında özelleştirmenize olanak tanır. fallbackRender
prop'u, hata ve hata bilgisini argüman olarak alır, bu da daha spesifik hata mesajları göstermenize veya hataya göre başka eylemler gerçekleştirmenize olanak tanır.
2. Yüksek Mertebeli Bileşen (HOC) Olarak Hata Sınırı
Başka bir bileşeni bir Hata Sınırı ile saran bir yüksek mertebeli bileşen (HOC) oluşturabilirsiniz. Bu, aynı kodu tekrarlamak zorunda kalmadan birden çok bileşene Hata Sınırları uygulamak için yararlı olabilir.
function withErrorBoundary(WrappedComponent) {
return class WithErrorBoundary extends React.Component {
render() {
return (
<ErrorBoundary>
<WrappedComponent {...this.props} />
</ErrorBoundary>
);
}
};
}
// Kullanım:
const MyComponentWithErrorHandling = withErrorBoundary(MyComponentThatMightThrow);
withErrorBoundary
fonksiyonu, bir bileşeni argüman olarak alır ve orijinal bileşeni bir Hata Sınırı ile saran yeni bir bileşen döndürür. Bu, uygulamanızdaki herhangi bir bileşene kolayca hata yönetimi eklemenizi sağlar.
Hata Sınırlarını Test Etme
Hata Sınırlarınızın doğru çalıştığından emin olmak için onları test etmek önemlidir. Hata Sınırlarınızı test etmek için Jest ve React Testing Library gibi test kütüphanelerini kullanabilirsiniz.
İşte React Testing Library kullanarak bir Hata Sınırının nasıl test edileceğine dair bir örnek:
import { render, screen, fireEvent } from '@testing-library/react';
import ErrorBoundary from './ErrorBoundary';
function ComponentThatThrows() {
throw new Error('Bu bileşen bir hata fırlatıyor');
}
test('bir hata fırlatıldığında yedek arayüzü render eder', () => {
render(
<ErrorBoundary>
<ComponentThatThrows />
</ErrorBoundary>
);
expect(screen.getByText('Bir şeyler ters gitti.')).toBeInTheDocument();
});
Bu test, bir hata fırlatan ComponentThatThrows
bileşenini render eder. Test daha sonra Hata Sınırı tarafından render edilen yedek arayüzün görüntülendiğini doğrular.
Hata Sınırları ve Sunucu Bileşenleri (React 18+)
React 18 ve sonrasında Sunucu Bileşenlerinin (Server Components) tanıtılmasıyla birlikte, Hata Sınırları hata yönetiminde hayati bir rol oynamaya devam ediyor. Sunucu Bileşenleri sunucuda çalışır ve yalnızca render edilmiş çıktıyı istemciye gönderir. Temel ilkeler aynı kalsa da, dikkate alınması gereken birkaç nüans vardır:
- Sunucu Tarafı Hata Kaydı: Sunucu Bileşenleri içinde meydana gelen hataları sunucuda kaydettiğinizden emin olun. Bu, sunucu tarafı bir kayıt (logging) çerçevesi kullanmayı veya hataları bir hata izleme servisine göndermeyi içerebilir.
- İstemci Tarafı Yedekleme: Sunucu Bileşenleri sunucuda render edilse de, hatalar durumunda istemci tarafında bir yedek arayüz sağlamanız gerekir. Bu, sunucu bileşeni render edemese bile kullanıcının tutarlı bir deneyim yaşamasını sağlar.
- Akışlı SSR (Streaming SSR): Akışlı Sunucu Tarafı Oluşturma (SSR) kullanırken, akış işlemi sırasında hatalar meydana gelebilir. Hata Sınırları, etkilenen akış için bir yedek arayüz render ederek bu hataları zarif bir şekilde yönetmenize yardımcı olabilir.
Sunucu Bileşenlerinde hata yönetimi gelişen bir alandır, bu nedenle en son en iyi uygulamalar ve önerilerle güncel kalmak önemlidir.
Kaçınılması Gereken Yaygın Hatalar
- Hata Sınırlarına aşırı güvenme: Hata Sınırlarını, bileşenlerinizdeki uygun hata yönetiminin yerine kullanmayın. Her zaman hataları zarif bir şekilde yöneten sağlam ve güvenilir kod yazmaya çalışın.
- Hataları görmezden gelme: Hata Sınırları tarafından yakalanan hataları kaydettiğinizden emin olun, böylece sorunların temel nedenini bulabilirsiniz. Sadece bir yedek arayüz gösterip hatayı görmezden gelmeyin.
- Doğrulama Hataları için Hata Sınırları kullanma: Hata Sınırları, doğrulama hatalarını yönetmek için doğru araç değildir. Bunun yerine daha spesifik doğrulama teknikleri kullanın.
- Hata Sınırlarını test etmeme: Doğru çalıştıklarından emin olmak için Hata Sınırlarınızı test edin.
Sonuç
Hata Sınırları, sağlam ve güvenilir React uygulamaları oluşturmak için güçlü bir araçtır. Hata Sınırlarını nasıl etkili bir şekilde uygulayacağınızı ve kullanacağınızı anlayarak, kullanıcı deneyimini iyileştirebilir, uygulama çökmelerini önleyebilir ve hata ayıklamayı basitleştirebilirsiniz. Hata Sınırlarını stratejik olarak yerleştirmeyi, bilgilendirici yedek arayüz sağlamayı, hataları kaydetmeyi ve Hata Sınırlarınızı kapsamlı bir şekilde test etmeyi unutmayın.
Bu rehberde özetlenen yönergeleri ve en iyi uygulamaları takip ederek, React uygulamalarınızın hatalara karşı dirençli olmasını ve kullanıcılarınıza olumlu bir deneyim sunmasını sağlayabilirsiniz.