Pelajari cara menggunakan React ErrorBoundary untuk menangani error dengan baik, mencegah aplikasi crash, dan memberikan pengalaman pengguna yang lebih baik dengan strategi pemulihan yang tangguh.
React ErrorBoundary: Strategi Isolasi dan Pemulihan Error
Dalam dunia pengembangan front-end yang dinamis, terutama saat bekerja dengan kerangka kerja berbasis komponen yang kompleks seperti React, error yang tidak terduga tidak dapat dihindari. Error ini, jika tidak ditangani dengan benar, dapat menyebabkan aplikasi crash dan pengalaman pengguna yang membuat frustrasi. Komponen ErrorBoundary React menawarkan solusi yang tangguh untuk menangani error ini dengan baik, mengisolasinya, dan menyediakan strategi pemulihan. Panduan komprehensif ini mengeksplorasi kekuatan ErrorBoundary, menunjukkan cara mengimplementasikannya secara efektif untuk membangun aplikasi React yang lebih tangguh dan ramah pengguna untuk audiens global.
Memahami Kebutuhan Error Boundary
Sebelum masuk ke implementasi, mari kita pahami mengapa error boundary sangat penting. Di React, error yang terjadi selama rendering, dalam metode siklus hidup (lifecycle methods), atau dalam konstruktor komponen anak berpotensi merusak seluruh aplikasi. Hal ini karena error yang tidak tertangkap akan menyebar ke atas pohon komponen, sering kali menyebabkan layar kosong atau pesan error yang tidak membantu. Bayangkan seorang pengguna di Jepang mencoba menyelesaikan transaksi keuangan penting, hanya untuk menemukan layar kosong karena error kecil di komponen yang tampaknya tidak terkait. Ini menggambarkan kebutuhan krusial akan manajemen error yang proaktif.
Error boundary menyediakan cara untuk menangkap error JavaScript di mana pun di pohon komponen anaknya, mencatat error tersebut, dan menampilkan UI fallback alih-alih merusak pohon komponen. Mereka memungkinkan Anda untuk mengisolasi komponen yang rusak dan mencegah error di satu bagian aplikasi Anda memengaruhi bagian lain, memastikan pengalaman pengguna yang lebih stabil dan andal secara global.
Apa itu React ErrorBoundary?
ErrorBoundary adalah komponen React yang menangkap error JavaScript di mana pun di pohon komponen anaknya, mencatat error tersebut, dan menampilkan UI fallback. Ini adalah komponen kelas yang mengimplementasikan salah satu atau kedua metode siklus hidup berikut:
static getDerivedStateFromError(error): Metode siklus hidup ini dipanggil setelah error dilemparkan oleh komponen turunan. Ia menerima error yang dilemparkan sebagai argumen dan harus mengembalikan nilai untuk memperbarui state komponen.componentDidCatch(error, info): Metode siklus hidup ini dipanggil setelah error dilemparkan oleh komponen turunan. Ia menerima dua argumen: error yang dilemparkan dan objek info yang berisi informasi tentang komponen mana yang melemparkan error. Anda dapat menggunakan metode ini untuk mencatat informasi error atau melakukan efek samping lainnya.
Membuat Komponen ErrorBoundary Dasar
Mari kita buat komponen ErrorBoundary dasar untuk mengilustrasikan prinsip-prinsip fundamental.
Contoh Kode
Berikut adalah kode untuk komponen ErrorBoundary sederhana:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {
hasError: false,
error: null,
errorInfo: null,
};
}
static getDerivedStateFromError(error) {
// Perbarui state agar render berikutnya akan menampilkan UI fallback.
return {
hasError: true,
};
}
componentDidCatch(error, info) {
// Contoh "componentStack":
// di ComponentThatThrows (dibuat oleh App)
// di App
console.error("Menangkap sebuah error:", error);
console.error("Info error:", info.componentStack);
this.setState({ error: error, errorInfo: info });
// Anda juga bisa mencatat error ke layanan pelaporan error
// logErrorToMyService(error, info.componentStack);
}
render() {
if (this.state.hasError) {
// Anda bisa merender UI fallback kustom apa pun
return (
Terjadi kesalahan.
Error: {this.state.error && this.state.error.toString()}
{this.state.errorInfo && this.state.errorInfo.componentStack}
);
}
return this.props.children;
}
}
export default ErrorBoundary;
Penjelasan
- Konstruktor: Konstruktor menginisialisasi state komponen dengan
hasErrordiatur kefalse. Kami juga menyimpan error dan errorInfo untuk tujuan debugging. getDerivedStateFromError(error): Metode statis ini dipanggil ketika sebuah error dilemparkan oleh komponen anak. Ia memperbarui state untuk menunjukkan bahwa error telah terjadi.componentDidCatch(error, info): Metode ini dipanggil setelah error dilemparkan. Ia menerima error dan objekinfoyang berisi informasi tentang tumpukan komponen (component stack). Di sini, kami mencatat error ke konsol (ganti dengan mekanisme pencatatan pilihan Anda, seperti Sentry, Bugsnag, atau solusi internal kustom). Kami juga mengatur error dan errorInfo di dalam state.render(): Metode render memeriksa statehasError. Jikatrue, ia merender UI fallback; jika tidak, ia merender anak-anak komponen. UI fallback harus informatif dan ramah pengguna. Menyertakan detail error dan tumpukan komponen, meskipun membantu bagi pengembang, harus dirender secara kondisional atau dihapus di lingkungan produksi karena alasan keamanan.
Menggunakan Komponen ErrorBoundary
Untuk menggunakan komponen ErrorBoundary, cukup bungkus komponen apa pun yang mungkin melemparkan error di dalamnya.
Contoh Kode
import ErrorBoundary from './ErrorBoundary';
function MyComponent() {
return (
{/* Komponen yang mungkin melemparkan error */}
);
}
function App() {
return (
);
}
export default App;
Penjelasan
Dalam contoh ini, MyComponent dibungkus dengan ErrorBoundary. Jika terjadi error di dalam MyComponent atau anak-anaknya, ErrorBoundary akan menangkapnya dan merender UI fallback.
Strategi ErrorBoundary Tingkat Lanjut
Meskipun ErrorBoundary dasar menyediakan tingkat penanganan error yang fundamental, ada beberapa strategi tingkat lanjut yang dapat Anda terapkan untuk meningkatkan manajemen error Anda.
1. Error Boundary Granular
Alih-alih membungkus seluruh aplikasi dengan satu ErrorBoundary, pertimbangkan untuk menggunakan error boundary granular. Ini melibatkan penempatan komponen ErrorBoundary di sekitar bagian-bagian spesifik aplikasi Anda yang lebih rentan terhadap error atau di mana kegagalan akan memiliki dampak terbatas. Misalnya, Anda mungkin membungkus widget individual atau komponen yang bergantung pada sumber data eksternal.
Contoh
function ProductList() {
return (
{/* Daftar produk */}
);
}
function RecommendationWidget() {
return (
{/* Mesin rekomendasi */}
);
}
function App() {
return (
);
}
Dalam contoh ini, RecommendationWidget memiliki ErrorBoundary-nya sendiri. Jika mesin rekomendasi gagal, itu tidak akan memengaruhi ProductList, dan pengguna masih dapat menjelajahi produk. Pendekatan granular ini meningkatkan pengalaman pengguna secara keseluruhan dengan mengisolasi error dan mencegahnya menyebar ke seluruh aplikasi.
2. Pencatatan dan Pelaporan Error
Mencatat error sangat penting untuk debugging dan mengidentifikasi masalah yang berulang. Metode siklus hidup componentDidCatch adalah tempat yang ideal untuk berintegrasi dengan layanan pencatatan error seperti Sentry, Bugsnag, atau Rollbar. Layanan ini menyediakan laporan error terperinci, termasuk jejak tumpukan (stack traces), konteks pengguna, dan informasi lingkungan, yang memungkinkan Anda untuk mendiagnosis dan menyelesaikan masalah dengan cepat. Pertimbangkan untuk menganonimkan atau menyunting data pengguna yang sensitif sebelum mengirim log error untuk memastikan kepatuhan terhadap peraturan privasi seperti GDPR.
Contoh
import * as Sentry from "@sentry/react";
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {
hasError: false,
};
}
static getDerivedStateFromError(error) {
// Perbarui state agar render berikutnya akan menampilkan UI fallback.
return {
hasError: true,
};
}
componentDidCatch(error, info) {
// Catat error ke Sentry
Sentry.captureException(error, { extra: info });
// Anda juga bisa mencatat error ke layanan pelaporan error
console.error("Menangkap sebuah error:", error);
}
render() {
if (this.state.hasError) {
// Anda bisa merender UI fallback kustom apa pun
return (
Terjadi kesalahan.
);
}
return this.props.children;
}
}
export default ErrorBoundary;
Dalam contoh ini, metode componentDidCatch menggunakan Sentry.captureException untuk melaporkan error ke Sentry. Anda dapat mengonfigurasi Sentry untuk mengirim notifikasi ke tim Anda, memungkinkan Anda merespons dengan cepat terhadap error kritis.
3. UI Fallback Kustom
UI fallback yang ditampilkan oleh ErrorBoundary adalah kesempatan untuk memberikan pengalaman yang ramah pengguna bahkan ketika terjadi error. Alih-alih menampilkan pesan error generik, pertimbangkan untuk menampilkan pesan yang lebih informatif yang membimbing pengguna menuju solusi. Ini mungkin termasuk instruksi tentang cara menyegarkan halaman, menghubungi dukungan, atau mencoba lagi nanti. Anda juga dapat menyesuaikan UI fallback berdasarkan jenis error yang terjadi.
Contoh
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {
hasError: false,
error: null,
};
}
static getDerivedStateFromError(error) {
// Perbarui state agar render berikutnya akan menampilkan UI fallback.
return {
hasError: true,
error: error,
};
}
componentDidCatch(error, info) {
console.error("Menangkap sebuah error:", error);
// Anda juga bisa mencatat error ke layanan pelaporan error
// logErrorToMyService(error, info.componentStack);
}
render() {
if (this.state.hasError) {
// Anda bisa merender UI fallback kustom apa pun
if (this.state.error instanceof NetworkError) {
return (
Error Jaringan
Silakan periksa koneksi internet Anda dan coba lagi.
);
} else {
return (
Terjadi kesalahan.
Silakan coba segarkan halaman atau hubungi dukungan.
);
}
}
return this.props.children;
}
}
export default ErrorBoundary;
Dalam contoh ini, UI fallback memeriksa apakah error tersebut adalah NetworkError. Jika ya, ia menampilkan pesan spesifik yang menginstruksikan pengguna untuk memeriksa koneksi internet mereka. Jika tidak, ia menampilkan pesan error generik. Memberikan panduan yang spesifik dan dapat ditindaklanjuti dapat sangat meningkatkan pengalaman pengguna.
4. Mekanisme Coba Lagi (Retry)
Dalam beberapa kasus, error bersifat sementara dan dapat diselesaikan dengan mencoba kembali operasi tersebut. Anda dapat mengimplementasikan mekanisme coba lagi di dalam ErrorBoundary untuk secara otomatis mencoba kembali operasi yang gagal setelah penundaan tertentu. Ini bisa sangat berguna untuk menangani error jaringan atau pemadaman server sementara. Berhati-hatilah dalam mengimplementasikan mekanisme coba lagi untuk operasi yang mungkin memiliki efek samping, karena mencobanya kembali dapat menyebabkan konsekuensi yang tidak diinginkan.
Contoh
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(`Error HTTP! status: ${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; // Penundaan eksponensial
console.log(`Mencoba lagi dalam ${retryDelay / 1000} detik...`);
const timer = setTimeout(fetchData, retryDelay);
return () => clearTimeout(timer); // Bersihkan timer saat unmount atau re-render
}
if (!data) {
fetchData();
}
}, [error, retryCount, data]);
if (isLoading) {
return Memuat data...
;
}
if (error) {
return Error: {error.message} - Dicoba ulang {retryCount} kali.
;
}
return Data: {JSON.stringify(data)}
;
}
function App() {
return (
);
}
export default App;
Dalam contoh ini, DataFetchingComponent mencoba mengambil data dari API. Jika terjadi error, ia menambah retryCount dan mencoba kembali operasi setelah penundaan yang meningkat secara eksponensial. ErrorBoundary menangkap setiap pengecualian yang tidak tertangani dan menampilkan pesan error, termasuk jumlah upaya coba lagi.
5. Error Boundary dan Server-Side Rendering (SSR)
Saat menggunakan Server-Side Rendering (SSR), penanganan error menjadi lebih kritis. Error yang terjadi selama proses rendering di sisi server dapat merusak seluruh server, yang menyebabkan waktu henti (downtime) dan pengalaman pengguna yang buruk. Anda perlu memastikan bahwa error boundary Anda dikonfigurasi dengan benar untuk menangani error baik di server maupun di klien. Seringkali, kerangka kerja SSR seperti Next.js dan Remix memiliki mekanisme penanganan error bawaan mereka sendiri yang melengkapi React Error Boundary.
6. Menguji Error Boundary
Menguji error boundary sangat penting untuk memastikan mereka berfungsi dengan benar dan menyediakan UI fallback yang diharapkan. Gunakan pustaka pengujian seperti Jest dan React Testing Library untuk mensimulasikan kondisi error dan memverifikasi bahwa error boundary Anda menangkap error dan merender UI fallback yang sesuai. Pertimbangkan untuk menguji berbagai jenis error dan kasus tepi untuk memastikan error boundary Anda tangguh dan menangani berbagai skenario.
Contoh
import { render, screen } from '@testing-library/react';
import ErrorBoundary from './ErrorBoundary';
function ComponentThatThrows() {
throw new Error('Komponen ini melemparkan sebuah error');
return Ini seharusnya tidak dirender
;
}
test('merender UI fallback saat sebuah error dilemparkan', () => {
render(
);
const errorMessage = screen.getByText(/Terjadi kesalahan/i);
expect(errorMessage).toBeInTheDocument();
});
Tes ini merender komponen yang melemparkan error di dalam ErrorBoundary. Kemudian, ia memverifikasi bahwa UI fallback dirender dengan benar dengan memeriksa apakah pesan error ada di dalam dokumen.
7. Degradasi yang Anggun (Graceful Degradation)
Error boundary adalah komponen kunci dalam mengimplementasikan degradasi yang anggun (graceful degradation) di aplikasi React Anda. Degradasi yang anggun adalah praktik merancang aplikasi Anda agar tetap berfungsi, meskipun dengan fungsionalitas yang berkurang, bahkan ketika sebagian darinya gagal. Error boundary memungkinkan Anda untuk mengisolasi komponen yang gagal dan mencegahnya memengaruhi sisa aplikasi. Dengan menyediakan UI fallback dan fungsionalitas alternatif, Anda dapat memastikan bahwa pengguna masih dapat mengakses fitur-fitur penting bahkan ketika terjadi error.
Kesalahan Umum yang Harus Dihindari
Meskipun ErrorBoundary adalah alat yang ampuh, ada beberapa kesalahan umum yang harus dihindari:
- Tidak membungkus kode asinkron:
ErrorBoundaryhanya menangkap error selama rendering, dalam metode siklus hidup, dan dalam konstruktor. Error dalam kode asinkron (misalnya,setTimeout,Promises) perlu ditangkap menggunakan bloktry...catchdan ditangani dengan tepat di dalam fungsi asinkron tersebut. - Penggunaan Error Boundary yang berlebihan: Hindari membungkus sebagian besar aplikasi Anda dalam satu
ErrorBoundary. Hal ini dapat menyulitkan untuk mengisolasi sumber error dan dapat menyebabkan UI fallback generik ditampilkan terlalu sering. Gunakan error boundary granular untuk mengisolasi komponen atau fitur tertentu. - Mengabaikan Informasi Error: Jangan hanya menangkap error dan menampilkan UI fallback. Pastikan untuk mencatat informasi error (termasuk tumpukan komponen) ke layanan pelaporan error atau konsol Anda. Ini akan membantu Anda mendiagnosis dan memperbaiki masalah yang mendasarinya.
- Menampilkan Informasi Sensitif di Produksi: Hindari menampilkan informasi error yang terperinci (misalnya, jejak tumpukan) di lingkungan produksi. Hal ini dapat mengekspos informasi sensitif kepada pengguna dan dapat menjadi risiko keamanan. Sebaliknya, tampilkan pesan error yang ramah pengguna dan catat informasi terperinci ke layanan pelaporan error.
Error Boundary dengan Komponen Fungsional dan Hooks
Meskipun Error Boundary diimplementasikan sebagai komponen kelas, Anda masih dapat menggunakannya secara efektif untuk menangani error di dalam komponen fungsional yang menggunakan hook. Pendekatan tipikal melibatkan pembungkusan komponen fungsional di dalam komponen ErrorBoundary, seperti yang telah ditunjukkan sebelumnya. Logika penanganan error berada di dalam ErrorBoundary, yang secara efektif mengisolasi error yang mungkin terjadi selama rendering komponen fungsional atau eksekusi hook.
Secara spesifik, setiap error yang dilemparkan selama rendering komponen fungsional atau di dalam body hook useEffect akan ditangkap oleh ErrorBoundary. Namun, penting untuk dicatat bahwa ErrorBoundary tidak menangkap error yang terjadi di dalam event handler (misalnya, onClick, onChange) yang terpasang pada elemen DOM di dalam komponen fungsional. Untuk event handler, Anda harus terus menggunakan blok try...catch tradisional untuk penanganan error.
Internasionalisasi dan Lokalisasi Pesan Error
Saat mengembangkan aplikasi untuk audiens global, sangat penting untuk menginternasionalkan dan melokalkan pesan error Anda. Pesan error yang ditampilkan di UI fallback ErrorBoundary harus diterjemahkan ke dalam bahasa pilihan pengguna untuk memberikan pengalaman pengguna yang lebih baik. Anda dapat menggunakan pustaka seperti i18next atau React Intl untuk mengelola terjemahan Anda dan secara dinamis menampilkan pesan error yang sesuai berdasarkan lokal pengguna.
Contoh menggunakan i18next
import i18next from 'i18next';
import { useTranslation } from 'react-i18next';
i18next.init({
resources: {
en: {
translation: {
'error.generic': 'Terjadi kesalahan. Silakan coba lagi nanti.',
'error.network': 'Error jaringan. Silakan periksa koneksi internet Anda.',
},
},
fr: {
translation: {
'error.generic': 'Terjadi kesalahan. Silakan coba lagi nanti.',
'error.network': 'Error jaringan. Silakan periksa koneksi internet Anda.',
},
},
},
lng: 'en',
fallbackLng: 'en',
interpolation: {
escapeValue: false, // tidak diperlukan untuk react karena ia melakukan escape secara default
},
});
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);
static getDerivedStateFromError = (error) => {
// Perbarui state agar render berikutnya akan menampilkan UI fallback
// return { hasError: true }; // ini tidak berfungsi dengan hooks sebagaimana adanya
setHasError(true);
setError(error);
}
if (hasError) {
// Anda bisa merender UI fallback kustom apa pun
return ;
}
return children;
}
export default ErrorBoundary;
Dalam contoh ini, kami menggunakan i18next untuk mengelola terjemahan untuk bahasa Inggris dan Prancis. Komponen ErrorFallback menggunakan hook useTranslation untuk mengambil pesan error yang sesuai berdasarkan bahasa saat ini. Ini memastikan bahwa pengguna melihat pesan error dalam bahasa pilihan mereka, meningkatkan pengalaman pengguna secara keseluruhan.
Kesimpulan
Komponen React ErrorBoundary adalah alat penting untuk membangun aplikasi React yang tangguh dan ramah pengguna. Dengan mengimplementasikan error boundary, Anda dapat menangani error dengan baik, mencegah aplikasi crash, dan memberikan pengalaman pengguna yang lebih baik bagi pengguna di seluruh dunia. Dengan memahami prinsip-prinsip error boundary, menerapkan strategi tingkat lanjut seperti error boundary granular, pencatatan error, dan UI fallback kustom, serta menghindari kesalahan umum, Anda dapat membangun aplikasi React yang lebih tangguh dan andal yang memenuhi kebutuhan audiens global. Ingatlah untuk mempertimbangkan internasionalisasi dan lokalisasi saat menampilkan pesan error untuk memberikan pengalaman pengguna yang benar-benar inklusif. Seiring dengan semakin meningkatnya kompleksitas aplikasi web, penguasaan teknik penanganan error akan menjadi semakin penting bagi pengembang yang membangun perangkat lunak berkualitas tinggi.