Error handling is a critical part of any application that interacts with external APIs. Well-implemented error handling:
When working with APIs, a number of things can go wrong: network issues, server problems, invalid requests, authentication failures, etc. Properly handling these errors ensures your application remains functional and user-friendly even when things don't go as expected.
HTTP status codes are standardized responses from servers that indicate the outcome of an HTTP request. They are grouped into five classes:
// Common HTTP Status Codes 200 - OK // Request succeeded 201 - Created // Resource created successfully 400 - Bad Request // Server couldn't understand the request 401 - Unauthorized // Authentication required 403 - Forbidden // Server understood but refuses to authorize 404 - Not Found // Resource not found 500 - Internal Server Error // Server encountered an unexpected condition
When working with the Fetch API in React, it's important to understand that fetch doesn't automatically reject on HTTP error statuses. You need to check if the response is ok (status in 200-299 range) and throw an error manually if needed.
Best practices for error handling with fetch:
// Complete Error Handling Example import { useState, useEffect } from 'react'; function DataFetcher() { const [data, setData] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); useEffect(() => { const fetchData = async () => { setLoading(true); setError(null); try { const response = await fetch('https://api.example.com/data'); // Check if the response is ok (status in the range 200-299) if (!response.ok) { // Get more details about the error const errorData = await response.json().catch(() => null); throw new Error( errorData?.message || `HTTP error! status: ${response.status}` ); } const data = await response.json(); setData(data); } catch (err) { setError(err.message || 'Something went wrong'); // You might also want to log the error for debugging console.error('Fetch error:', err); } finally { setLoading(false); } }; fetchData(); }, []); if (loading) return <p>Loading...</p> if (error) return <p>Error: {error}</p> if (!data) return <p>No data available</p> return ( <div> {/* Render your data here */} <pre>{JSON.stringify(data, null, 2)}</pre> </div> ); }
Error boundaries are React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of crashing the component tree.
Key points about error boundaries:
// Example of Error Boundary Component import React from 'react'; class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false, error: null, errorInfo: null }; } static getDerivedStateFromError(error) { // Update state so the next render will show the fallback UI return { hasError: true }; } componentDidCatch(error, errorInfo) { // You can log the error to an error reporting service console.error('Error caught by ErrorBoundary:', error, errorInfo); this.setState({ error: error, errorInfo: errorInfo }); } render() { if (this.state.hasError) { // You can render any custom fallback UI return ( <div className="error-container"> <h2>Something went wrong.</h2> {this.props.fallback || <p>Please try again later.</p>} {process.env.NODE_ENV === 'development' && ( <details style={{ whiteSpace: 'pre-wrap' }}> {this.state.error && this.state.error.toString()} <br /> {this.state.errorInfo && this.state.errorInfo.componentStack} </details> )} </div> ); } return this.props.children; } } // Usage function App() { return ( <ErrorBoundary> <MyComponent /> </ErrorBoundary> ); }
Complete the API Error Handling code-along to practice implementing error handling in a React application.
Starter Code Solution Code