Skip to the chapter
←ReactGGroundwork⚛React
React/Intermediate/I9
20 / 57 written chapters
/

The cover
  1. B1Setup & JSX
  2. B2Thinking in React
  3. B3Components
  4. B4Props
  5. B5useState
  6. B6Events & conditionals
  7. B7Lists & keys
  8. B8Forms
  9. B9useEffect
  10. B10Lifting state & styling
  11. B11Guided project
  12. ★Cheat page
  1. I1useRef
  2. I2Context
  3. I3useReducer
  4. I4Custom hooks
  5. I5Effects in depth
  6. I6Routing
  7. I7State architecture
  8. I8Memoisation
  9. I9Boundaries & portals
  10. I10Data fetching
  11. I11Forms at scale
  12. I12State libraries
  13. I13Auth
  14. I14TanStack Query
  15. I15Testing
  16. I16TypeScript
  17. I17Animation
  18. I18E2E testing
  19. I19i18n
  20. I20File uploads
  21. I21Real-time UI
  22. I22shadcn/ui
  23. ★Cheat page
  1. A1Fiber
  2. A2Concurrent
  3. A3Suspense
  4. A4Server Components
  5. A5Server Actions
  6. A6use()
  7. A7Rendering strategies
  8. A8Effect timing
  9. A9External stores
  10. A10Refs in depth
  11. A11Component patterns
  12. A12Performance
  13. A13Accessibility
  14. A14Design systems
  15. A15Advanced TypeScript
  16. A16Architecture at scale
  17. A17Custom renderers
  18. A18Migrating & upgrading
  19. A19Production debugging
  20. A20Review & judgement
  21. A21State machines
  22. A22Feature flags
  23. A23Interview bank
  24. ★Cheat page

⚛I9 · Part 2 · Intermediate

Error boundaries, portals & keys in depth

Containing a crash, escaping the DOM tree, and using keys on purpose.

3 min read7 sections4 exercises
20 of 57

In this chapter

  • Why one broken component blanks the page
  • Error boundaries are still classes
  • What they do and do not catch
  • Where to put them
  • Portals: rendering somewhere else in the DOM

and 2 more

about 3 min
07On this page
  1. Why one broken component blanks the page
  2. Error boundaries are still classes
  3. What they do and do not catch
  4. Where to put them
  5. Portals: rendering somewhere else in the DOM
  6. A modal is not just a portal
  7. Keys as a deliberate tool

Why one broken component blanks the page

Since React 16, an error thrown during render that nothing catches unmounts the entire tree. That is deliberate: a half-rendered UI showing stale or wrong data is considered worse than showing nothing. So you have to decide where the damage stops, and that is an error boundary.

Error boundaries are still classes

class ErrorBoundary extends React.Component {
  state = { error: null };

  static getDerivedStateFromError(error) {
    return { error };                     // render the fallback
  }

  componentDidCatch(error, info) {
    logToService(error, info.componentStack);   // report it
  }

  render() {
    if (this.state.error) return this.props.fallback;
    return this.props.children;
  }
}

There is no hook equivalent, which is the one remaining reason to write a class in React. In practice you write it once, or install react-error-boundary and get a reset function with it.

What they do and do not catch

CaughtNot caught
Errors during renderErrors in event handlers
Errors in lifecycle methodsErrors in setTimeout or a promise
Errors in constructors of the tree belowErrors in the boundary itself
Server-side rendering errors

Handlers and async code are not caught because React is not on the stack when they run — there is no render to abandon. Use try/catch there and set error state yourself.

Where to put them

<ErrorBoundary fallback={<FullPageError />}>        // last resort
  <Layout>
    <ErrorBoundary fallback={<WidgetError />}>      // one panel fails alone
      <RevenueChart />
    </ErrorBoundary>
    <Outlet />
  </Layout>
</ErrorBoundary>

Granularity is the whole design decision. One boundary at the root turns any bug into a blank page; a boundary around each independent panel means a broken chart leaves the rest of the dashboard usable. Put them where a failure is survivable.

Give the fallback a way out — a retry button that resets the boundary's state, or a link home. A dead end with "Something went wrong" is barely better than the blank page.

Portals: rendering somewhere else in the DOM

createPortal(children, domNode)

function Modal({ children, onClose }) {
  return createPortal(
    <div className="overlay" onClick={onClose}>
      <div className="modal">{children}</div>
    </div>,
    document.body
  );
}

The modal is still a child in the React tree — it receives context, its state belongs to its parent, and events bubble to its React parent, not its DOM parent. Only the DOM placement changes.

The problem portals actually solve

A modal with z-index: 9999 can still render behind a header if an ancestor created a stacking context — which happens with transform, opacity below 1, filter or will-change, not just position. No z-index inside that context can escape it. The same applies to overflow: hidden clipping a dropdown. A portal moves the node out of the offending subtree entirely.

A modal is not just a portal

The DOM placement is the easy part. A correct dialog also needs focus moved in on open, focus trapped while it is open, focus returned to the trigger on close, Escape to dismiss, the background made inert, and role="dialog" aria-modal="true" with a label.

<dialog ref={ref} onClose={onClose}>   // the browser does most of it

The native <dialog> element with showModal() handles focus, Escape, inertness and the top layer for free — and being in the top layer means it does not need a portal at all. Worth reaching for before a library.

Keys as a deliberate tool

Beyond lists, changing a key is how you tell React "this is a different instance now". React unmounts the old component and mounts a fresh one, resetting all of its state.

<ProfileForm key={userId} user={user} />      // switching user clears the draft
<ErrorBoundary key={location.pathname}>      // navigating clears a caught error
<Chart key={dataVersion} data={data} />      // force a third-party widget to rebuild

Each of these replaces an effect that watched a value and reset something. Fewer moving parts, and no render with the stale value first.

The cost is real: everything below unmounts, so scroll position, focus and any in-flight work in that subtree are discarded. Use it when you want exactly that, not as a way to force a refresh.

Interview answer, one sentence

"An uncaught render error unmounts the whole tree, so boundaries are how you decide where a failure stops — per panel, not just at the root, and they catch render errors but never handlers or async code. Portals change the DOM parent without changing the React parent, which is how you escape a stacking context or an overflow: hidden."

04Practice this chapter

Opens in the editor: write it, run it, and check it against real tests.

  • 8 testsWhen should an error boundary reset?intermediate
  • 10 testsAn error boundary with a way outintermediate
  • 10 testsAn accessible modal in a portalintermediate
  • 5 testsA tooltip rendered outside the trigger's DOM subtreeintermediate
All 66 for this level →
I9

Finished reading?

0 of 57 chapters read in this series

→Next stop

  1. I8Previous · I8Memoisation
  2. I9you are here
  3. Next · I10Data fetching patternsEvery bug in hand-rolled fetching is one of five, and they are all solved problems.4 min read

01On this page

7 sections · 0%

  1. 01Why one broken component blanks the page
  2. 02Error boundaries are still classes
  3. 03What they do and do not catch
  4. 04Where to put them
  5. 05Portals: rendering somewhere else in the DOM
  6. 06A modal is not just a portal
  7. 07Keys as a deliberate tool

[] chapters · t top