Na stacji kosmicznej awaria jednego systemu nie może oznaczac konca misji. Każdy krytyczny system ma kopie zapasowe, a zaloga jest przeszkolona do pracy w trybie awaryjnym. Twoją aplikacja React powinna dzialac tak samo - nawet jeśli cos pojdzie nie tak, użytkownik powinien moc kontynuowac prace.
Graceful degradation (lagodna degradacja) to filozofia projektowania, w której aplikacja kontynuuje działanie nawet gdy niektore jej części zawiodą - po prostu z obnizonym poziomem funkcjonalności.
1// Zamiast: awaria całej strony
2function BrokenApp() {
3 return <WhiteScreenOfDeath />; // Użytkownik nie może nic zrobic
4}
5
6// Graceful degradation: części aplikacji nadal działają
7function ResilientApp() {
8 return (
9 <div>
10 <Navigation /> {/* Zawsze działa */}
11 <ErrorBoundary fallback={<FallbackContent />}>
12 <MainContent /> {/* Moze sie zepsuc */}
13 </ErrorBoundary>
14 <Footer /> {/* Zawsze działa */}
15 </div>
16 );
17}Gdy operacja sie nie powiedzie, daj użytkownikowi możliwość ponownej proby:
1import { ErrorBoundary } from 'react-error-boundary';
2
3function ErrorFallbackWithRetry({ error, resetErrorBoundary }) {
4 return (
5 <div className="error-panel">
6 <h3>Awaria modulu</h3>
7 <p>Błąd: {error.message}</p>
8 <button onClick={resetErrorBoundary}>
9 Ponowna proba
10 </button>
11 </div>
12 );
13}
14
15function App() {
16 return (
17 <ErrorBoundary
18 FallbackComponent={ErrorFallbackWithRetry}
19 onReset={() => {
20 // Wyczysc cache, zresetuj stan itp.
21 }}
22 >
23 <DataPanel />
24 </ErrorBoundary>
25 );
26}Zamiast pokazywac błąd, pokaz alternatywna treść:
1function WeatherWidget() {
2 const [data, setData] = useState(null);
3 const [error, setError] = useState(false);
4
5 useEffect(() => {
6 fetchWeather()
7 .then(setData)
8 .catch(() => setError(true));
9 }, []);
10
11 if (error) {
12 // Zamiast błędu, pokaz statyczne dane
13 return (
14 <div className="weather-widget offline">
15 <p>Dane pogodowe niedostepne</p>
16 <p>Ostatnia aktualizacja: 2h temu</p>
17 <small>Pracujemy nad przywroceniem połączenia</small>
18 </div>
19 );
20 }
21
22 if (!data) return <WeatherSkeleton />;
23 return <WeatherDisplay data={data} />;
24}Obsluguj czesciowe awarie - gdy niektorych danych nie można pobrać:
1function MissionDashboard() {
2 const [missions, setMissions] = useState([]);
3 const [stats, setStats] = useState(null);
4 const [statsError, setStatsError] = useState(false);
5
6 useEffect(() => {
7 // Pobierz dane równoległe
8 Promise.allSettled([
9 fetch('/api/missions').then(r => r.json()),
10 fetch('/api/stats').then(r => r.json())
11 ]).then(([missionsResult, statsResult]) => {
12 if (missionsResult.status === 'fulfilled') {
13 setMissions(missionsResult.value);
14 }
15 if (statsResult.status === 'fulfilled') {
16 setStats(statsResult.value);
17 } else {
18 setStatsError(true);
19 }
20 });
21 }, []);
22
23 return (
24 <div>
25 <h2>Panel misji</h2>
26
27 {/* Statystyki - mogą byc niedostepne */}
28 {statsError ? (
29 <p className="warning">Statystyki chwilowo niedostepne</p>
30 ) : stats ? (
31 <StatsPanel data={stats} />
32 ) : (
33 <StatsSkeleton />
34 )}
35
36 {/* Lista misji - zawsze probujemy wyswietlic */}
37 <MissionList missions={missions} />
38 </div>
39 );
40}Dla krytycznych danych, implementuj automatyczne ponowne proby z rosnącymi opoznieniami:
1async function fetchWithRetry(url, maxRetries = 3) {
2 for (let attempt = 0; attempt < maxRetries; attempt++) {
3 try {
4 const response = await fetch(url);
5 if (!response.ok) throw new Error('Request failed');
6 return await response.json();
7 } catch (error) {
8 if (attempt === maxRetries - 1) throw error;
9 // Exponential backoff: 1s, 2s, 4s
10 const delay = Math.pow(2, attempt) * 1000;
11 await new Promise(resolve => setTimeout(resolve, delay));
12 }
13 }
14}
15
16function CriticalData() {
17 const [data, setData] = useState(null);
18 const [error, setError] = useState(null);
19 const [retrying, setRetrying] = useState(false);
20
21 const loadData = async () => {
22 setRetrying(true);
23 setError(null);
24 try {
25 const result = await fetchWithRetry('/api/critical-data');
26 setData(result);
27 } catch (err) {
28 setError(err);
29 } finally {
30 setRetrying(false);
31 }
32 };
33
34 useEffect(() => { loadData(); }, []);
35
36 if (error) return (
37 <div>
38 <p>Nie udalo sie pobrac danych po 3 probach</p>
39 <button onClick={loadData}>Sprobuj ponownie</button>
40 </div>
41 );
42 if (retrying) return <p>Ponawiam probe połączenia...</p>;
43 if (!data) return <LoadingSkeleton />;
44 return <DataDisplay data={data} />;
45}Najlepsza praktyka to łączenie obu mechanizmow:
1<ErrorBoundary FallbackComponent={ErrorUI}>
2 <Suspense fallback={<LoadingSkeleton />}>
3 <LazyComponent />
4 </Suspense>
5</ErrorBoundary>Error Boundary przechwytuje błędy, a Suspense obsługuje ładowanie. Razem tworzą kompletny system bezpieczenstwa.