Falytom logoFALYTOM
Games
ToolsUI ComponentsServicesProductsAbout
LoginSign Up
menu
  1. Home
  2. /Web Development
  3. /React
  4. /Error Handling
Your Progress0%
0 of 42 completed

React Topics

Getting Started

  • What is React?
  • React vs Vanilla JavaScript
  • Setting Up Your Environment
  • Your First React App

JSX Fundamentals

  • Introduction to JSX
  • JSX Expressions and Variables
  • Conditional Rendering
  • Lists and Keys
  • Styling in JSX

Components Basics

  • Function Components
  • Props
  • Children Prop
  • Props Destructuring
  • Component Composition

State Management

  • Introduction to State
  • useState Hook
  • State Updates and Re-renders
  • Multiple State Variables
  • State Best Practices

Events and Interactivity

  • Handling Events
  • Event Objects
  • Forms in React
  • Form Validation

Side Effects and Data

  • Introduction to useEffect
  • Data Fetching
  • Cleanup Functions
  • Dependency Arrays

Advanced Hooks

  • useRef Hook
  • useContext Hook
  • useMemo Hook
  • useCallback Hook

React Patterns

  • Custom Hooks
  • Higher-Order Components
  • Render Props Pattern

React Router

  • Introduction to React Router
  • Routes and Navigation
  • Dynamic Routes and Parameters

Best Practices

  • Component Organization
  • Performance Optimization
  • Error Handling
  • Testing React Components

Real World Project

  • Building a Complete App

Error Handling

Building resilient React applications

Errors happen in every application - network failures, unexpected data, bugs, and user mistakes. Without proper error handling, a single error can crash your entire React app, leaving users with a blank screen. In this lesson, you'll learn how to catch and handle errors gracefully using error boundaries, implement recovery strategies, and provide great user experiences even when things go wrong. Let's build resilient React applications! ๐Ÿ›ก๏ธ

Why Error Handling Matters

Without Error Handling

no-error-handling.jsx
function UserProfile({ userId }) {
  const [user, setUser] = React.useState(null)
  
  React.useEffect(() => {
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(data => setUser(data))
    // โŒ No error handling!
  }, [userId])
  
  // โŒ What if user is null or undefined?
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  )
}

// Problems:
// - App crashes if fetch fails
// - App crashes if user is null
// - User sees blank screen
// - No feedback about what went wrong

With Error Handling

with-error-handling.jsx
function UserProfile({ userId }) {
  const [user, setUser] = React.useState(null)
  const [loading, setLoading] = React.useState(true)
  const [error, setError] = React.useState(null)
  
  React.useEffect(() => {
    setLoading(true)
    setError(null)
    
    fetch(`/api/users/${userId}`)
      .then(res => {
        if (!res.ok) throw new Error('Failed to fetch user')
        return res.json()
      })
      .then(data => {
        setUser(data)
        setLoading(false)
      })
      .catch(err => {
        setError(err.message)
        setLoading(false)
      })
  }, [userId])
  
  if (loading) return <div>Loading...</div>
  if (error) return <div>Error: {error}</div>
  if (!user) return <div>User not found</div>
  
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  )
}

// Benefits:
// โœ… App doesn't crash
// โœ… User sees helpful messages
// โœ… Clear loading state
// โœ… Graceful degradation

Error Boundaries

Error boundaries are React components that catch JavaScript errors anywhere in their child component tree:

Creating an Error Boundary

error-boundary.jsx
import React from 'react'

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props)
    this.state = { hasError: false, error: null, errorInfo: null }
  }
  
  // Called when error is thrown
  static getDerivedStateFromError(error) {
    // Update state to show fallback UI
    return { hasError: true }
  }
  
  // Called after error is caught
  componentDidCatch(error, errorInfo) {
    // Log error to error reporting service
    console.error('Error caught by boundary:', error, errorInfo)
    
    // Store error details
    this.setState({
      error: error,
      errorInfo: errorInfo
    })
    
    // Send to error tracking service
    // logErrorToService(error, errorInfo)
  }
  
  render() {
    if (this.state.hasError) {
      // Fallback UI
      return (
        <div className="error-container">
          <h1>Something went wrong</h1>
          <details style={{ whiteSpace: 'pre-wrap' }}>
            {this.state.error && this.state.error.toString()}
            <br />
            {this.state.errorInfo.componentStack}
          </details>
        </div>
      )
    }
    
    // No error, render children normally
    return this.props.children
  }
}

export default ErrorBoundary

// Usage:
function App() {
  return (
    <ErrorBoundary>
      <UserDashboard />
    </ErrorBoundary>
  )
}

// If UserDashboard or any child throws error:
// 1. Error boundary catches it
// 2. Shows fallback UI
// 3. Rest of app keeps working!

Better Error Boundary with Reset

error-boundary-reset.jsx
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props)
    this.state = { hasError: false, error: null }
  }
  
  static getDerivedStateFromError(error) {
    return { hasError: true, error }
  }
  
  componentDidCatch(error, errorInfo) {
    console.error('Error:', error, errorInfo)
  }
  
  // Reset error state
  resetError = () => {
    this.setState({ hasError: false, error: null })
  }
  
  render() {
    if (this.state.hasError) {
      return (
        <div className="error-boundary">
          <h2>Oops! Something went wrong</h2>
          <p>{this.state.error?.message}</p>
          <button onClick={this.resetError}>
            Try Again
          </button>
        </div>
      )
    }
    
    return this.props.children
  }
}

// With custom fallback:
function App() {
  return (
    <ErrorBoundary fallback={<CustomErrorPage />}>
      <Dashboard />
    </ErrorBoundary>
  )
}

What Error Boundaries Catch:

  • โœ“ Errors in render methods
  • โœ“ Errors in lifecycle methods
  • โœ“ Errors in constructor
  • โœ“ Errors in child components

What Error Boundaries DON'T Catch:

  • โœ— Event handlers (use try-catch)
  • โœ— Async code (setTimeout, promises)
  • โœ— Server-side rendering errors
  • โœ— Errors in the boundary itself

Where to Place Error Boundaries

Strategy 1: Top-Level Boundary

top-level.jsx
// Catches all errors, shows full-page error
function App() {
  return (
    <ErrorBoundary fallback={<FullPageError />}>
      <Router>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/dashboard" element={<Dashboard />} />
        </Routes>
      </Router>
    </ErrorBoundary>
  )
}

// Pros: Simple, catches everything
// Cons: Any error breaks entire app

Strategy 2: Route-Level Boundaries

route-level.jsx
// Each route has its own boundary
function App() {
  return (
    <Router>
      <Routes>
        <Route 
          path="/" 
          element={
            <ErrorBoundary fallback={<RouteError />}>
              <Home />
            </ErrorBoundary>
          } 
        />
        <Route 
          path="/dashboard" 
          element={
            <ErrorBoundary fallback={<RouteError />}>
              <Dashboard />
            </ErrorBoundary>
          } 
        />
      </Routes>
    </Router>
  )
}

// Pros: Errors isolated to routes
// Cons: More boundaries to manage

Strategy 3: Component-Level Boundaries (Best)

component-level.jsx
// Boundaries around critical features
function Dashboard() {
  return (
    <div>
      <Header />
      
      <ErrorBoundary fallback={<SectionError section="stats" />}>
        <UserStats />
      </ErrorBoundary>
      
      <ErrorBoundary fallback={<SectionError section="activity" />}>
        <RecentActivity />
      </ErrorBoundary>
      
      <ErrorBoundary fallback={<SectionError section="notifications" />}>
        <Notifications />
      </ErrorBoundary>
    </div>
  )
}

// Pros: 
// โœ… Fine-grained error isolation
// โœ… Rest of page works if one section fails
// โœ… Better UX

// Best practice: Multiple boundaries at different levels!

Handling Async Errors

Error boundaries don't catch async errors. Use try-catch:

Async/Await with Try-Catch

async-try-catch.jsx
function UserProfile({ userId }) {
  const [user, setUser] = React.useState(null)
  const [loading, setLoading] = React.useState(false)
  const [error, setError] = React.useState(null)
  
  React.useEffect(() => {
    async function fetchUser() {
      setLoading(true)
      setError(null)
      
      try {
        const response = await fetch(`/api/users/${userId}`)
        
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`)
        }
        
        const data = await response.json()
        setUser(data)
      } catch (err) {
        setError(err.message)
        console.error('Failed to fetch user:', err)
      } finally {
        setLoading(false)
      }
    }
    
    fetchUser()
  }, [userId])
  
  if (loading) return <LoadingSpinner />
  if (error) return <ErrorMessage error={error} />
  if (!user) return <NotFound />
  
  return <UserCard user={user} />
}

// Pattern: Always wrap async calls in try-catch!

Event Handler Errors

event-handler-errors.jsx
function SubmitForm() {
  const [error, setError] = React.useState(null)
  const [success, setSuccess] = React.useState(false)
  
  const handleSubmit = async (e) => {
    e.preventDefault()
    setError(null)
    setSuccess(false)
    
    try {
      const formData = new FormData(e.target)
      const response = await fetch('/api/submit', {
        method: 'POST',
        body: formData
      })
      
      if (!response.ok) {
        throw new Error('Submission failed')
      }
      
      setSuccess(true)
    } catch (err) {
      setError(err.message)
      // Optionally: Log to error service
    }
  }
  
  return (
    <form onSubmit={handleSubmit}>
      {error && <div className="error">{error}</div>}
      {success && <div className="success">Submitted!</div>}
      
      <input name="email" type="email" required />
      <button type="submit">Submit</button>
    </form>
  )
}

// Error boundaries DON'T catch event handler errors
// Always use try-catch in event handlers!

Interactive Demo

ReactPlayground.jsx
Code Editor
Preview

๐Ÿ’ก Tip: Edit the code above and click "Run" to see your changes

Error Recovery Patterns

1. Retry Pattern

retry-pattern.jsx
function DataFetcher({ url }) {
  const [data, setData] = React.useState(null)
  const [error, setError] = React.useState(null)
  const [retryCount, setRetryCount] = React.useState(0)
  
  const fetchData = async () => {
    try {
      const response = await fetch(url)
      if (!response.ok) throw new Error('Fetch failed')
      const data = await response.json()
      setData(data)
      setError(null)
    } catch (err) {
      setError(err.message)
    }
  }
  
  React.useEffect(() => {
    fetchData()
  }, [retryCount])
  
  const handleRetry = () => {
    setRetryCount(count => count + 1)
  }
  
  if (error) {
    return (
      <div>
        <p>Error: {error}</p>
        <button onClick={handleRetry}>Retry</button>
      </div>
    )
  }
  
  return data ? <DisplayData data={data} /> : <Loading />
}

2. Fallback Content Pattern

fallback-pattern.jsx
function UserAvatar({ userId }) {
  const [user, setUser] = React.useState(null)
  const [error, setError] = React.useState(false)
  
  React.useEffect(() => {
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(setUser)
      .catch(() => setError(true))
  }, [userId])
  
  if (error) {
    // Show default avatar when error
    return <DefaultAvatar />
  }
  
  return user ? <img src={user.avatar} /> : <LoadingAvatar />
}

// Graceful degradation: Show something instead of nothing

3. Optimistic UI Pattern

optimistic-ui.jsx
function TodoList() {
  const [todos, setTodos] = React.useState([])
  
  const addTodo = async (text) => {
    // Optimistically add to UI
    const tempId = Date.now()
    const newTodo = { id: tempId, text, temp: true }
    setTodos([...todos, newTodo])
    
    try {
      // Try to save to server
      const response = await fetch('/api/todos', {
        method: 'POST',
        body: JSON.stringify({ text })
      })
      
      const savedTodo = await response.json()
      
      // Replace temp todo with real one
      setTodos(todos => 
        todos.map(t => t.id === tempId ? savedTodo : t)
      )
    } catch (err) {
      // Revert on error
      setTodos(todos => todos.filter(t => t.id !== tempId))
      alert('Failed to save todo')
    }
  }
  
  return <div>{/* UI */}</div>
}

// Show update immediately, revert if fails

User-Friendly Error Messages

error-messages.jsx
// โŒ Bad - Technical errors shown to users
"TypeError: Cannot read property 'name' of undefined"
"Network request failed with status 500"
"Uncaught ReferenceError: user is not defined"

// โœ… Good - Friendly, actionable messages
function getErrorMessage(error) {
  // Map technical errors to user-friendly messages
  if (error.message.includes('Network')) {
    return 'Unable to connect. Please check your internet connection.'
  }
  
  if (error.message.includes('404')) {
    return 'The requested item could not be found.'
  }
  
  if (error.message.includes('403')) {
    return 'You do not have permission to access this resource.'
  }
  
  if (error.message.includes('500')) {
    return 'Server error. Please try again later.'
  }
  
  // Generic fallback
  return 'Something went wrong. Please try again.'
}

function ErrorMessage({ error }) {
  const message = getErrorMessage(error)
  
  return (
    <div className="error-banner">
      <h3>Oops!</h3>
      <p>{message}</p>
      <button onClick={retry}>Try Again</button>
      <a href="/support">Contact Support</a>
    </div>
  )
}

// Tips for error messages:
// โœ… Clear and specific
// โœ… Suggest actions
// โœ… Avoid technical jargon
// โœ… Provide next steps
// โœ… Show empathy

Error Logging and Monitoring

Logging Errors

error-logging.jsx
// Error logging service
class ErrorLogger {
  static log(error, errorInfo, context = {}) {
    // Log to console in development
    if (process.env.NODE_ENV === 'development') {
      console.error('Error:', error)
      console.error('Error Info:', errorInfo)
      console.error('Context:', context)
    }
    
    // Send to error tracking service in production
    if (process.env.NODE_ENV === 'production') {
      // Example: Sentry, LogRocket, Bugsnag
      // Sentry.captureException(error, {
      //   contexts: { react: errorInfo },
      //   tags: context
      // })
    }
    
    // Store in local storage for debugging
    try {
      const errors = JSON.parse(localStorage.getItem('errors') || '[]')
      errors.push({
        error: error.toString(),
        errorInfo: errorInfo,
        context,
        timestamp: new Date().toISOString()
      })
      localStorage.setItem('errors', JSON.stringify(errors.slice(-10)))
    } catch (e) {
      // Ignore storage errors
    }
  }
}

// Usage in Error Boundary
class ErrorBoundary extends React.Component {
  componentDidCatch(error, errorInfo) {
    ErrorLogger.log(error, errorInfo, {
      userId: this.props.userId,
      route: window.location.pathname
    })
  }
  
  render() {
    // ...
  }
}

Integrating Error Tracking Services

error-services.jsx
// Sentry example
import * as Sentry from "@sentry/react"

// Initialize
Sentry.init({
  dsn: "your-sentry-dsn",
  environment: process.env.NODE_ENV,
  integrations: [new Sentry.BrowserTracing()],
  tracesSampleRate: 1.0,
})

// Error Boundary with Sentry
const SentryErrorBoundary = Sentry.ErrorBoundary

function App() {
  return (
    <SentryErrorBoundary fallback={<ErrorFallback />}>
      <YourApp />
    </SentryErrorBoundary>
  )
}

// Manual error reporting
try {
  riskyOperation()
} catch (error) {
  Sentry.captureException(error)
}

// Add context to errors
Sentry.setUser({ id: user.id, email: user.email })
Sentry.setTag("section", "checkout")

// Popular error tracking services:
// - Sentry (open source, popular)
// - LogRocket (session replay)
// - Bugsnag
// - Rollbar
// - TrackJS

Error Handling Best Practices

  • Use multiple error boundaries - Isolate errors to sections
  • Always catch async errors - Use try-catch in async functions
  • Show user-friendly messages - Hide technical details
  • Provide recovery options - Retry, go back, refresh
  • Log errors for debugging - Use error tracking services
  • Test error scenarios - Simulate failures
  • Handle network errors gracefully - Offline support
  • Validate data before use - Check for null/undefined
  • Set up error monitoring - Track errors in production
  • Document error handling strategy - For team consistency

Error Handling Checklist:

  • โœ“ Error boundaries at multiple levels
  • โœ“ Try-catch in all async functions
  • โœ“ Try-catch in all event handlers
  • โœ“ Graceful fallback UI
  • โœ“ User-friendly error messages
  • โœ“ Retry/recovery mechanisms
  • โœ“ Error logging/monitoring setup
  • โœ“ Null/undefined checks
  • โœ“ Network error handling
  • โœ“ Loading and error states

Key Takeaways

  • Error boundaries catch render errors in child components
  • Use getDerivedStateFromError for fallback UI
  • Use componentDidCatch for logging errors
  • Place boundaries at multiple levels for isolation
  • Error boundaries DON'T catch async or event handler errors
  • Always use try-catch for async/await code
  • Always use try-catch in event handlers
  • Provide retry and recovery options
  • Show user-friendly error messages
  • Log errors to monitoring services
  • Validate data before using it
  • Handle network failures gracefully
  • Test error scenarios during development

What's Next?

You now know how to handle errors gracefully in React applications! You understand error boundaries, async error handling, and recovery strategies.

In the final lesson, you'll learn about Testing React Components:

  • Introduction to testing with Jest
  • Using React Testing Library
  • Writing effective component tests
  • Testing user interactions
  • Testing best practices

Get ready to build reliable, well-tested React applications! ๐Ÿงช

Learning how to handle errors gracefully in React with error boundaries and recovery strategies!

Previous
Performance Optimization
Next
Testing React Components

Never Miss a New React Tutorial

Join 2,000+ developers learning React step-by-step. Get new tutorials, tips, and exclusive resources delivered to your inbox - completely FREE.

You might also like

UI Components

Ready-made components for your projects

Developer Tools

Boost productivity with handy generators

HTML Game

Learn HTML concepts through interactive gameplay

Join Our Web Development Training Program

Master frontend & backend development with hands-on projects.

React Tutorials

0 of 42 completed

Your Progress0%

Topics

Getting Started

  • What is React?
  • React vs Vanilla JavaScript
  • Setting Up Your Environment
  • Your First React App

JSX Fundamentals

  • Introduction to JSX
  • JSX Expressions and Variables
  • Conditional Rendering
  • Lists and Keys
  • Styling in JSX

Components Basics

  • Function Components
  • Props
  • Children Prop
  • Props Destructuring
  • Component Composition

State Management

  • Introduction to State
  • useState Hook
  • State Updates and Re-renders
  • Multiple State Variables
  • State Best Practices

Events and Interactivity

  • Handling Events
  • Event Objects
  • Forms in React
  • Form Validation

Side Effects and Data

  • Introduction to useEffect
  • Data Fetching
  • Cleanup Functions
  • Dependency Arrays

Advanced Hooks

  • useRef Hook
  • useContext Hook
  • useMemo Hook
  • useCallback Hook

React Patterns

  • Custom Hooks
  • Higher-Order Components
  • Render Props Pattern

React Router

  • Introduction to React Router
  • Routes and Navigation
  • Dynamic Routes and Parameters

Best Practices

  • Component Organization
  • Performance Optimization
  • Error Handling
  • Testing React Components

Real World Project

  • Building a Complete App
Falytom logoFALYTOM

Learn technologies like HTML, CSS, JavaScript, React, NodeJS and more through interactive games, tutorials, components and free tools. Transform your businesses with AI chatbots, SEO-optimized websites, and professional development services.

ยฉ 2025 Tomilola Group
  • Facebook logo
  • Twitter logo
  • Instagram logo
  • LinkedIn logo
  • GitHub logo
  • YouTube logo