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.
Reading progress
0% read
In this module
Error Boundary
Suspense
Compound Component
Render prop
0%