Error Boundaries

Class components that catch errors during rendering in React: getDerivedStateFromError, componentDidCatch, the scope of error boundaries, and what they don't catch, with simple examples.

Advanced 13 min
TR

Error Boundaries

In this lesson, we'll see our FIRST class component in the course. There's no way to write error boundaries with hooks in React -- they can only be written using class components. Everything you've learned so far (hooks, state, props) applied to function components; this is an exceptional, narrowly-scoped topic.

Writing a Basic Error Boundary

When a component throws an error during rendering, React normally unmounts the ENTIRE application (showing a blank screen). An error boundary prevents this:

import { Component } from "react";

// ÖNEMLİ: Error boundary'ler, bu kursta gördüğümüz İLK class component.
// React'te error boundary'leri hook'larla (fonksiyon component'lerle)
// yazmanın bir yolu YOK -- yalnızca class component'ler
// `static getDerivedStateFromError` ile bunu yapabiliyor. Bu yüzden bu
// TEK konuda, istisnai olarak bir class component kullanıyoruz.
class BasicErrorBoundaryExample extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  // Bir child component render sırasında hata FIRLATTIĞINDA, React bu
  // metodu çağırır -- döndürdüğü değer, yeni state olur.
  static getDerivedStateFromError() {
    return { hasError: true };
  }

  render() {
    if (this.state.hasError) {
      return <p>Something went wrong.</p>;
    }

    return this.props.children;
  }
}

static getDerivedStateFromError() is called by React when a child throws an error -- whatever it returns becomes the new state. The render() method shows either the normal children or a fallback message, based on the hasError state.

Logging the Error with componentDidCatch

getDerivedStateFromError is ONLY for showing the fallback UI -- sending the error somewhere (logging it) requires a separate method:

import { Component } from "react";

class ComponentDidCatchExample extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError() {
    return { hasError: true };
  }

  // getDerivedStateFromError, fallback UI'ı GÖSTERMEK için kullanılır;
  // componentDidCatch ise hatayı bir yere GÖNDERMEK (örneğin bir loglama
  // servisine) için kullanılır -- ikisi birlikte çalışabilir.
  componentDidCatch(error, errorInfo) {
    console.error("Caught an error:", error, errorInfo.componentStack);
  }

  render() {
    if (this.state.hasError) {
      return <p>Something went wrong.</p>;
    }

    return this.props.children;
  }
}

componentDidCatch(error, errorInfo) receives the error itself AND errorInfo.componentStack (a "stack trace" showing which component the error came from) -- in real applications, this is usually where a request is sent to an error-tracking service (like Sentry).

Using an Error Boundary

We use an error boundary to wrap components that might throw:

import { Component, useState } from "react";

class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError() {
    return { hasError: true };
  }

  render() {
    if (this.state.hasError) {
      return <p>Something went wrong.</p>;
    }

    return this.props.children;
  }
}

function BuggyCounter({ count }) {
  if (count === 3) {
    // Render sırasında bilinçli olarak bir hata fırlatıyoruz -- gerçek bir
    // uygulamada bu, beklenmedik bir `undefined.someProperty` gibi bir hata
    // olurdu.
    throw new Error("Count reached 3!");
  }

  return <p>Count: {count}</p>;
}

function UsingErrorBoundaryExample() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      {/* ErrorBoundary, İÇİNDEKİ herhangi bir component render sırasında
          hata fırlatırsa, o hatayı YAKALAR ve normal render'ı fallback
          UI'la DEĞİŞTİRİR -- BuggyCounter'ın kendisi hatayı yönetmek
          zorunda değil. */}
      <ErrorBoundary>
        <BuggyCounter count={count} />
      </ErrorBoundary>
    </div>
  );
}

BuggyCounter deliberately throws an error when count === 3 -- ErrorBoundary catches it and replaces the normal render with a fallback UI. BuggyCounter itself doesn't need to handle the error -- that's the error boundary's job.

The Scope of Error Boundaries

Using multiple, SMALL error boundaries is usually better than one big one:

import { Component } from "react";

class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError() {
    return { hasError: true };
  }

  render() {
    if (this.state.hasError) {
      return <p>{this.props.fallbackText}</p>;
    }

    return this.props.children;
  }
}

function BuggyWidget() {
  throw new Error("This widget is broken!");
}

function ErrorBoundaryScopeExample() {
  return (
    <div>
      {/* İki AYRI ErrorBoundary, iki AYRI bölümü sarmalıyor -- Sidebar
          çökse bile, MainContent bundan ETKİLENMEZ, kendi ErrorBoundary'si
          İÇİNDE kalır. Tek bir büyük ErrorBoundary kullansaydık, herhangi
          bir hata TÜM sayfayı "Something went wrong" mesajına
          çevirebilirdi. */}
      <ErrorBoundary fallbackText="Sidebar failed to load.">
        <BuggyWidget />
      </ErrorBoundary>
      <ErrorBoundary fallbackText="Main content failed to load.">
        <p>Main content (still works fine)</p>
      </ErrorBoundary>
    </div>
  );
}

Two separate ErrorBoundarys wrap two separate sections -- if one crashes, the other is NOT affected. If we used a single large boundary, any error could turn the ENTIRE page into a fallback message.

What Error Boundaries Don't Catch

Error boundaries have a limit -- they don't catch every kind of error:

import { Component, useState } from "react";

class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError() {
    return { hasError: true };
  }

  render() {
    if (this.state.hasError) {
      return <p>Something went wrong.</p>;
    }

    return this.props.children;
  }
}

function EventHandlerExample() {
  function handleClick() {
    // Error boundary'ler yalnızca RENDER sırasındaki hataları yakalar --
    // bir event handler İÇİNDE (bir onClick gibi) fırlatılan bir hata,
    // error boundary tarafından YAKALANMAZ. Bunun için normal try/catch
    // kullanmak gerekir.
    try {
      throw new Error("Button click failed!");
    } catch (error) {
      console.error("Caught manually:", error.message);
    }
  }

  return <button onClick={handleClick}>Click me</button>;
}

function WhatErrorBoundariesDontCatchExample() {
  const [showInfo, setShowInfo] = useState(false);

  return (
    <div>
      {/* ErrorBoundary burada EventHandlerExample'ı sarmalıyor -- ama
          içindeki onClick hatası yine de YAKALANMAYACAK, çünkü o bir
          event handler'da oluyor, render sırasında değil. */}
      <ErrorBoundary>
        <EventHandlerExample />
      </ErrorBoundary>
      <button onClick={() => setShowInfo(true)}>Show limitations</button>
      {showInfo && (
        <p>
          Error boundaries do NOT catch: event handler errors, errors in
          asynchronous code (setTimeout, fetch callbacks), errors during
          server-side rendering, or errors thrown in the boundary itself.
        </p>
      )}
    </div>
  );
}

Error boundaries only catch errors thrown during RENDERING -- they do NOT catch errors in event handlers (like onClick), asynchronous code (setTimeout, fetch callbacks), server-side rendering, or errors thrown in the boundary itself. Regular try/catch is used for errors in event handlers.

Summary and Glossary

An error boundary is a class component that defines static getDerivedStateFromError (to show a fallback UI) and optionally componentDidCatch (to log the error) -- React has no hook equivalent for this. An error boundary catches errors thrown during rendering by any component INSIDE it and replaces the normal render with a fallback UI. Using multiple small boundaries prevents an error in one section from affecting others. Error boundaries do NOT catch errors in event handlers, asynchronous code, or the boundary itself.

Glossary

Error Boundary — A class component that catches errors thrown during rendering by the components inside it and shows a fallback UI.

Fallback UI — Alternative UI shown in place of normal content during an error (or a loading state).