JSTAcademy
0 XP
Dashboard
Crash Courses
Error Boundaries & Patterns
10 min
PhD+200 XP
Crash Courses · PhD

Error Boundaries & Patterns

Handle errors gracefully and apply production React patterns at scale.
10 min read+200 XP on completionCert: React Crash Course
Tap any word in the text below to start reading from there.

Error Boundaries & Production Patterns

Error Boundaries — Graceful Failure

Without error boundaries, one JavaScript error in a component crashes the entire React app — the user sees a blank white screen with no recourse.

Error boundaries are class components (the only case where class components are still needed) that catch errors in their subtree and render fallback UI:

import { Component, ErrorInfo, ReactNode } from 'react'

interface Props {
  children: ReactNode
  fallback?: ReactNode
  onError?: (error: Error, info: ErrorInfo) => void
}

interface State { hasError: boolean; error?: Error }

class ErrorBoundary extends Component<Props, State> {
  state: State = { hasError: false }

  static getDerivedStateFromError(error: Error): State {
    return { hasError: true, error }
  }

  componentDidCatch(error: Error, info: ErrorInfo) {
    // Log to Sentry, DataDog, etc.
    this.props.onError?.(error, info)
    console.error('ErrorBoundary caught:', error, info)
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback ?? (
        <div className="error-screen">
          <h2>Something went wrong.</h2>
          <button onClick={() => this.setState({ hasError: false })}>
            Try again
          </button>
        </div>
      )
    }
    return this.props.children
  }
}

// Usage — wrap critical sections
<ErrorBoundary fallback={<ErrorScreen />} onError={logToSentry}>
  <CoursePlayer courseId={id} />
</ErrorBoundary>

Always wrap <Suspense> in an <ErrorBoundary> in production — if the lazy component fails to load, Suspense alone leaves an unhandled error.

Suspense + Lazy Loading — Full Pattern

const HeavyVideoPlayer = lazy(() => import('./HeavyVideoPlayer'))

function CoursePage({ courseId }: { courseId: string }) {
  return (
    <div>
      <CourseHeader courseId={courseId} />

      <ErrorBoundary fallback={<p>Player failed to load.</p>}>
        <Suspense fallback={<PlayerSkeleton />}>
          <HeavyVideoPlayer courseId={courseId} />
        </Suspense>
      </ErrorBoundary>
    </div>
  )
}

forwardRef — Expose DOM Access from Wrapper Components

interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
  label: string
  error?: string
}

const Input = forwardRef<HTMLInputElement, InputProps>(
  ({ label, error, ...props }, ref) => (
    <div className="input-field">
      <label>{label}</label>
      <input ref={ref} className={error ? 'error' : ''} {...props} />
      {error && <span className="error-text">{error}</span>}
    </div>
  )
)

Input.displayName = 'Input'  // shows in React DevTools

// Parent can access the real <input> DOM node
function LoginForm() {
  const emailRef = useRef<HTMLInputElement>(null)

  function handleSubmit() {
    emailRef.current?.focus()  // focus the Input's inner <input> element
  }

  return <Input ref={emailRef} label="Email" type="email" />
}

Compound Component Pattern

const TabsContext = createContext<{
  active: string
  setActive: (id: string) => void
} | null>(null)

function Tabs({ children, defaultTab }: { children: ReactNode; defaultTab: string }) {
  const [active, setActive] = useState(defaultTab)

  return (
    <TabsContext.Provider value={{ active, setActive }}>
      <div className="tabs">{children}</div>
    </TabsContext.Provider>
  )
}

Tabs.Tab = function Tab({ id, children }: { id: string; children: ReactNode }) {
  const ctx = useContext(TabsContext)!
  return (
    <button
      className={`tab ${ctx.active === id ? 'active' : ''}`}
      onClick={() => ctx.setActive(id)}
    >
      {children}
    </button>
  )
}

Tabs.Panel = function Panel({ id, children }: { id: string; children: ReactNode }) {
  const { active } = useContext(TabsContext)!
  if (active !== id) return null
  return <div className="tab-panel">{children}</div>
}

// Usage — explicit, readable, no prop spaghetti
<Tabs defaultTab="overview">
  <Tabs.Tab id="overview">Overview</Tabs.Tab>
  <Tabs.Tab id="modules">Modules</Tabs.Tab>
  <Tabs.Tab id="quiz">Quiz</Tabs.Tab>
  <Tabs.Panel id="overview"><OverviewContent /></Tabs.Panel>
  <Tabs.Panel id="modules"><ModuleList /></Tabs.Panel>
  <Tabs.Panel id="quiz"><QuizView /></Tabs.Panel>
</Tabs>

The compound component pattern is how libraries like Radix UI, Headless UI, and React Select are built — shared implicit state via context, explicit composition on the surface.

0%