[{"data":1,"prerenderedAt":1473},["ShallowReactive",2],{"blog-\u002Fblog\u002Freact-error-boundaries-guide":3},{"id":4,"title":5,"body":6,"description":1458,"difficulty":1459,"extension":1460,"framework":1461,"frameworkSlug":1462,"meta":1463,"navigation":756,"order":72,"path":1464,"qaPath":1465,"seo":1466,"stem":1467,"subtopic":1468,"topic":1469,"topicSlug":1470,"updated":1471,"__hash__":1472},"blog\u002Fblog\u002Freact-error-boundaries-guide.md","React Error Boundaries — Complete Interview Guide",{"type":7,"value":8,"toc":1439},"minimark",[9,14,18,25,151,154,158,165,170,177,213,220,224,238,272,290,294,297,304,308,319,363,476,480,483,504,511,515,518,650,657,661,668,672,677,730,920,924,934,1041,1045,1051,1134,1143,1147,1154,1166,1170,1177,1361,1365,1435],[10,11,13],"h2",{"id":12},"why-error-boundaries-exist","Why Error Boundaries Exist",[15,16,17],"p",{},"Before error boundaries landed in React 16, a single uncaught render error would unmount the entire React tree and leave users staring at a blank screen. There was no way to isolate the failure — one bad component took down everything.",[15,19,20,24],{},[21,22,23],"strong",{},"Error boundaries"," are React's answer to fault isolation. They act as circuit breakers: catch a render error in a subtree, display a fallback UI for that section, and let everything outside the boundary keep running normally.",[26,27,32],"pre",{"className":28,"code":29,"language":30,"meta":31,"style":31},"language-jsx shiki shiki-themes github-light github-dark","\u003CApp>\n  \u003CHeader \u002F>           {\u002F* unaffected — still renders *\u002F}\n  \u003CErrorBoundary fallback={\u003Cp>Widget failed.\u003C\u002Fp>}>\n    \u003CErrorWidget \u002F>    {\u002F* throws → fallback shown for this section only *\u002F}\n  \u003C\u002FErrorBoundary>\n  \u003CFooter \u002F>           {\u002F* unaffected — still renders *\u002F}\n\u003C\u002FApp>\n","jsx","",[33,34,35,51,70,100,117,127,141],"code",{"__ignoreMap":31},[36,37,40,44,48],"span",{"class":38,"line":39},"line",1,[36,41,43],{"class":42},"sVt8B","\u003C",[36,45,47],{"class":46},"sj4cs","App",[36,49,50],{"class":42},">\n",[36,52,54,57,60,63,67],{"class":38,"line":53},2,[36,55,56],{"class":42},"  \u003C",[36,58,59],{"class":46},"Header",[36,61,62],{"class":42}," \u002F>           {",[36,64,66],{"class":65},"sJ8bj","\u002F* unaffected — still renders *\u002F",[36,68,69],{"class":42},"}\n",[36,71,73,75,78,82,86,89,92,95,97],{"class":38,"line":72},3,[36,74,56],{"class":42},[36,76,77],{"class":46},"ErrorBoundary",[36,79,81],{"class":80},"sScJk"," fallback",[36,83,85],{"class":84},"szBVR","=",[36,87,88],{"class":42},"{\u003C",[36,90,15],{"class":91},"s9eBZ",[36,93,94],{"class":42},">Widget failed.\u003C\u002F",[36,96,15],{"class":91},[36,98,99],{"class":42},">}>\n",[36,101,103,106,109,112,115],{"class":38,"line":102},4,[36,104,105],{"class":42},"    \u003C",[36,107,108],{"class":46},"ErrorWidget",[36,110,111],{"class":42}," \u002F>    {",[36,113,114],{"class":65},"\u002F* throws → fallback shown for this section only *\u002F",[36,116,69],{"class":42},[36,118,120,123,125],{"class":38,"line":119},5,[36,121,122],{"class":42},"  \u003C\u002F",[36,124,77],{"class":46},[36,126,50],{"class":42},[36,128,130,132,135,137,139],{"class":38,"line":129},6,[36,131,56],{"class":42},[36,133,134],{"class":46},"Footer",[36,136,62],{"class":42},[36,138,66],{"class":65},[36,140,69],{"class":42},[36,142,144,147,149],{"class":38,"line":143},7,[36,145,146],{"class":42},"\u003C\u002F",[36,148,47],{"class":46},[36,150,50],{"class":42},[15,152,153],{},"This is why interviewers ask about them: they touch React internals (lifecycle phases, class components, concurrent mode), real-world fault tolerance, and external service integration (Sentry).",[10,155,157],{"id":156},"the-two-lifecycle-methods","The Two Lifecycle Methods",[15,159,160,161,164],{},"An error boundary is a ",[21,162,163],{},"class component"," that implements one or both of two lifecycle methods:",[166,167,169],"h3",{"id":168},"getderivedstatefromerror","getDerivedStateFromError",[15,171,172,173,176],{},"Called during the ",[21,174,175],{},"render phase"," when a descendant throws. Receives the error and must return a state update — typically a flag that switches the component to render its fallback.",[26,178,180],{"className":28,"code":179,"language":30,"meta":31,"style":31},"static getDerivedStateFromError(error) {\n  return { hasError: true, error }; \u002F\u002F pure — no side effects\n}\n",[33,181,182,192,209],{"__ignoreMap":31},[36,183,184,187,189],{"class":38,"line":39},[36,185,186],{"class":42},"static ",[36,188,169],{"class":80},[36,190,191],{"class":42},"(error) {\n",[36,193,194,197,200,203,206],{"class":38,"line":53},[36,195,196],{"class":84},"  return",[36,198,199],{"class":42}," { hasError: ",[36,201,202],{"class":46},"true",[36,204,205],{"class":42},", error }; ",[36,207,208],{"class":65},"\u002F\u002F pure — no side effects\n",[36,210,211],{"class":38,"line":72},[36,212,69],{"class":42},[15,214,215,216,219],{},"It runs during rendering so it must be ",[21,217,218],{},"pure"," — no console.log, no fetch calls.",[166,221,223],{"id":222},"componentdidcatch","componentDidCatch",[15,225,172,226,229,230,233,234,237],{},[21,227,228],{},"commit phase",", after the fallback has been painted. Receives both the error and an ",[33,231,232],{},"info"," object with ",[33,235,236],{},"componentStack"," — the React component call stack. This is the correct place for side effects like error logging.",[26,239,241],{"className":28,"code":240,"language":30,"meta":31,"style":31},"componentDidCatch(error, info) {\n  logErrorToSentry(error, {\n    extra: { componentStack: info.componentStack },\n  });\n}\n",[33,242,243,250,258,263,268],{"__ignoreMap":31},[36,244,245,247],{"class":38,"line":39},[36,246,223],{"class":80},[36,248,249],{"class":42},"(error, info) {\n",[36,251,252,255],{"class":38,"line":53},[36,253,254],{"class":80},"  logErrorToSentry",[36,256,257],{"class":42},"(error, {\n",[36,259,260],{"class":38,"line":72},[36,261,262],{"class":42},"    extra: { componentStack: info.componentStack },\n",[36,264,265],{"class":38,"line":102},[36,266,267],{"class":42},"  });\n",[36,269,270],{"class":38,"line":119},[36,271,69],{"class":42},[15,273,274,275,277,278,282,283,285,286,289],{},"The two methods are complementary: ",[33,276,169],{}," decides ",[279,280,281],"em",{},"what"," to show; ",[33,284,223],{}," handles the ",[279,287,288],{},"side effects",". Use both.",[10,291,293],{"id":292},"why-class-components-are-required","Why Class Components Are Required",[15,295,296],{},"Error boundaries require these two lifecycle methods, which have no hook equivalents in React's public API. As of React 18 you still need a class component somewhere in the chain. The React team has acknowledged the gap but has not shipped a hook-based alternative.",[15,298,299,300,303],{},"In practice, the ",[33,301,302],{},"react-error-boundary"," library wraps a class boundary behind a clean functional API — this is the recommended approach for modern codebases that want to avoid writing class components directly.",[10,305,307],{"id":306},"what-error-boundaries-do-not-catch","What Error Boundaries Do NOT Catch",[15,309,310,311,314,315,318],{},"This is a favourite interview question. Boundaries catch errors during ",[21,312,313],{},"rendering, constructors, and lifecycle methods"," in the subtree below them. They do ",[21,316,317],{},"not"," catch:",[320,321,322,337,351,357],"ul",{},[323,324,325,328,329,332,333,336],"li",{},[21,326,327],{},"Event handlers"," — ",[33,330,331],{},"onClick",", ",[33,334,335],{},"onChange",", etc. run outside React's render cycle. Use try\u002Fcatch inside them.",[323,338,339,342,343,346,347,350],{},[21,340,341],{},"Async code"," — errors in ",[33,344,345],{},"setTimeout",", resolved\u002Frejected Promises, ",[33,348,349],{},"async\u002Fawait"," are not caught because they happen outside React's call stack.",[323,352,353,356],{},[21,354,355],{},"Server-side rendering"," — boundaries work client-side only.",[323,358,359,362],{},[21,360,361],{},"Errors in the boundary itself"," — a boundary cannot catch its own render errors; those propagate to the next ancestor boundary.",[26,364,366],{"className":28,"code":365,"language":30,"meta":31,"style":31},"function BadButton() {\n  const handleClick = () => {\n    try {\n      riskyOperation(); \u002F\u002F boundary WON'T catch this — use try\u002Fcatch\n    } catch (err) {\n      reportError(err);\n    }\n  };\n  return \u003Cbutton onClick={handleClick}>Go\u003C\u002Fbutton>;\n}\n",[33,367,368,379,399,406,417,428,436,441,447,471],{"__ignoreMap":31},[36,369,370,373,376],{"class":38,"line":39},[36,371,372],{"class":84},"function",[36,374,375],{"class":80}," BadButton",[36,377,378],{"class":42},"() {\n",[36,380,381,384,387,390,393,396],{"class":38,"line":53},[36,382,383],{"class":84},"  const",[36,385,386],{"class":80}," handleClick",[36,388,389],{"class":84}," =",[36,391,392],{"class":42}," () ",[36,394,395],{"class":84},"=>",[36,397,398],{"class":42}," {\n",[36,400,401,404],{"class":38,"line":72},[36,402,403],{"class":84},"    try",[36,405,398],{"class":42},[36,407,408,411,414],{"class":38,"line":102},[36,409,410],{"class":80},"      riskyOperation",[36,412,413],{"class":42},"(); ",[36,415,416],{"class":65},"\u002F\u002F boundary WON'T catch this — use try\u002Fcatch\n",[36,418,419,422,425],{"class":38,"line":119},[36,420,421],{"class":42},"    } ",[36,423,424],{"class":84},"catch",[36,426,427],{"class":42}," (err) {\n",[36,429,430,433],{"class":38,"line":129},[36,431,432],{"class":80},"      reportError",[36,434,435],{"class":42},"(err);\n",[36,437,438],{"class":38,"line":143},[36,439,440],{"class":42},"    }\n",[36,442,444],{"class":38,"line":443},8,[36,445,446],{"class":42},"  };\n",[36,448,450,452,455,458,461,463,466,468],{"class":38,"line":449},9,[36,451,196],{"class":84},[36,453,454],{"class":42}," \u003C",[36,456,457],{"class":91},"button",[36,459,460],{"class":80}," onClick",[36,462,85],{"class":84},[36,464,465],{"class":42},"{handleClick}>Go\u003C\u002F",[36,467,457],{"class":91},[36,469,470],{"class":42},">;\n",[36,472,474],{"class":38,"line":473},10,[36,475,69],{"class":42},[10,477,479],{"id":478},"boundary-granularity","Boundary Granularity",[15,481,482],{},"Where you place boundaries determines how much of the UI survives a failure. A good strategy uses three levels:",[484,485,486,492,498],"ol",{},[323,487,488,491],{},[21,489,490],{},"Root level"," — a catch-all backstop. If everything else fails, prevent a blank screen.",[323,493,494,497],{},[21,495,496],{},"Route\u002Fpage level"," — a boundary per route. A broken settings page doesn't kill the dashboard.",[323,499,500,503],{},[21,501,502],{},"Widget\u002Ffeature level"," — around autonomous sections (recommendation panels, ad slots, live feeds) that can fail without affecting main content.",[15,505,506,507,510],{},"The rule: place a boundary wherever you can write a ",[21,508,509],{},"meaningful, scoped fallback message",". The finer the boundary, the better the user experience.",[10,512,514],{"id":513},"fallback-ui-design","Fallback UI Design",[15,516,517],{},"A good fallback tells users what happened, what they can do next, and ideally provides a recovery action.",[26,519,521],{"className":28,"code":520,"language":30,"meta":31,"style":31},"function FallbackUI({ error, resetErrorBoundary }) {\n  return (\n    \u003Cdiv role=\"alert\" className=\"error-panel\">\n      \u003Ch2>This section couldn't load.\u003C\u002Fh2>\n      \u003Cp>Try refreshing or come back later.\u003C\u002Fp>\n      \u003Cbutton onClick={resetErrorBoundary}>Try again\u003C\u002Fbutton>\n      {\u002F* Never expose error.message in production *\u002F}\n    \u003C\u002Fdiv>\n  );\n}\n",[33,522,523,545,552,578,592,605,622,632,641,646],{"__ignoreMap":31},[36,524,525,527,530,533,537,539,542],{"class":38,"line":39},[36,526,372],{"class":84},[36,528,529],{"class":80}," FallbackUI",[36,531,532],{"class":42},"({ ",[36,534,536],{"class":535},"s4XuR","error",[36,538,332],{"class":42},[36,540,541],{"class":535},"resetErrorBoundary",[36,543,544],{"class":42}," }) {\n",[36,546,547,549],{"class":38,"line":53},[36,548,196],{"class":84},[36,550,551],{"class":42}," (\n",[36,553,554,556,559,562,564,568,571,573,576],{"class":38,"line":72},[36,555,105],{"class":42},[36,557,558],{"class":91},"div",[36,560,561],{"class":80}," role",[36,563,85],{"class":84},[36,565,567],{"class":566},"sZZnC","\"alert\"",[36,569,570],{"class":80}," className",[36,572,85],{"class":84},[36,574,575],{"class":566},"\"error-panel\"",[36,577,50],{"class":42},[36,579,580,583,585,588,590],{"class":38,"line":102},[36,581,582],{"class":42},"      \u003C",[36,584,10],{"class":91},[36,586,587],{"class":42},">This section couldn't load.\u003C\u002F",[36,589,10],{"class":91},[36,591,50],{"class":42},[36,593,594,596,598,601,603],{"class":38,"line":119},[36,595,582],{"class":42},[36,597,15],{"class":91},[36,599,600],{"class":42},">Try refreshing or come back later.\u003C\u002F",[36,602,15],{"class":91},[36,604,50],{"class":42},[36,606,607,609,611,613,615,618,620],{"class":38,"line":129},[36,608,582],{"class":42},[36,610,457],{"class":91},[36,612,460],{"class":80},[36,614,85],{"class":84},[36,616,617],{"class":42},"{resetErrorBoundary}>Try again\u003C\u002F",[36,619,457],{"class":91},[36,621,50],{"class":42},[36,623,624,627,630],{"class":38,"line":143},[36,625,626],{"class":42},"      {",[36,628,629],{"class":65},"\u002F* Never expose error.message in production *\u002F",[36,631,69],{"class":42},[36,633,634,637,639],{"class":38,"line":443},[36,635,636],{"class":42},"    \u003C\u002F",[36,638,558],{"class":91},[36,640,50],{"class":42},[36,642,643],{"class":38,"line":449},[36,644,645],{"class":42},"  );\n",[36,647,648],{"class":38,"line":473},[36,649,69],{"class":42},[15,651,652,653,656],{},"Keep fallback UI ",[21,654,655],{},"dead simple"," — never fetch data or render lazily inside it. If the fallback itself throws, the error propagates to the next ancestor boundary. Plain HTML is safest.",[10,658,660],{"id":659},"error-recovery","Error Recovery",[15,662,663,664,667],{},"Recovery works by resetting the boundary's error state so it re-renders its children. The key subtlety: if nothing changed since the crash, the component will throw again immediately. Pair the reset with a data refetch or a ",[33,665,666],{},"key"," change to give the retry a real chance to succeed.",[10,669,671],{"id":670},"the-react-error-boundary-library","The react-error-boundary Library",[15,673,674,676],{},[33,675,302],{}," is the community standard. It provides:",[320,678,679,704,715,722],{},[323,680,681,686,687,332,690,332,693,332,696,699,700,703],{},[21,682,683],{},[33,684,685],{},"\u003CErrorBoundary>"," with ",[33,688,689],{},"FallbackComponent",[33,691,692],{},"fallback",[33,694,695],{},"onError",[33,697,698],{},"onReset",", and ",[33,701,702],{},"resetKeys"," props",[323,705,706,710,711,714],{},[21,707,708],{},[33,709,689],{}," receives ",[33,712,713],{},"{ error, resetErrorBoundary }"," — the fallback can trigger recovery without prop drilling",[323,716,717,721],{},[21,718,719],{},[33,720,702],{}," — an array of values; when any changes, the boundary auto-resets (data-driven recovery)",[323,723,724,729],{},[21,725,726],{},[33,727,728],{},"useErrorBoundary()"," — lets functional components escalate async errors into the nearest boundary",[26,731,733],{"className":28,"code":732,"language":30,"meta":31,"style":31},"import { ErrorBoundary } from 'react-error-boundary';\n\n\u003CErrorBoundary\n  FallbackComponent={({ error, resetErrorBoundary }) => (\n    \u003Cdiv role=\"alert\">\n      \u003Cp>Error: {error.message}\u003C\u002Fp>\n      \u003Cbutton onClick={resetErrorBoundary}>Retry\u003C\u002Fbutton>\n    \u003C\u002Fdiv>\n  )}\n  resetKeys={[userId]}       \u002F\u002F auto-reset when user switches\n  onError={(err, info) => Sentry.captureException(err, { extra: info })}\n>\n  \u003CUserProfile userId={userId} \u002F>\n\u003C\u002FErrorBoundary>\n",[33,734,735,752,758,765,788,802,815,832,840,845,858,890,895,911],{"__ignoreMap":31},[36,736,737,740,743,746,749],{"class":38,"line":39},[36,738,739],{"class":84},"import",[36,741,742],{"class":42}," { ErrorBoundary } ",[36,744,745],{"class":84},"from",[36,747,748],{"class":566}," 'react-error-boundary'",[36,750,751],{"class":42},";\n",[36,753,754],{"class":38,"line":53},[36,755,757],{"emptyLinePlaceholder":756},true,"\n",[36,759,760,762],{"class":38,"line":72},[36,761,43],{"class":42},[36,763,764],{"class":46},"ErrorBoundary\n",[36,766,767,770,772,775,777,779,781,784,786],{"class":38,"line":102},[36,768,769],{"class":80},"  FallbackComponent",[36,771,85],{"class":84},[36,773,774],{"class":42},"{({ ",[36,776,536],{"class":535},[36,778,332],{"class":42},[36,780,541],{"class":535},[36,782,783],{"class":42}," }) ",[36,785,395],{"class":84},[36,787,551],{"class":42},[36,789,790,792,794,796,798,800],{"class":38,"line":119},[36,791,105],{"class":42},[36,793,558],{"class":91},[36,795,561],{"class":80},[36,797,85],{"class":84},[36,799,567],{"class":566},[36,801,50],{"class":42},[36,803,804,806,808,811,813],{"class":38,"line":129},[36,805,582],{"class":42},[36,807,15],{"class":91},[36,809,810],{"class":42},">Error: {error.message}\u003C\u002F",[36,812,15],{"class":91},[36,814,50],{"class":42},[36,816,817,819,821,823,825,828,830],{"class":38,"line":143},[36,818,582],{"class":42},[36,820,457],{"class":91},[36,822,460],{"class":80},[36,824,85],{"class":84},[36,826,827],{"class":42},"{resetErrorBoundary}>Retry\u003C\u002F",[36,829,457],{"class":91},[36,831,50],{"class":42},[36,833,834,836,838],{"class":38,"line":443},[36,835,636],{"class":42},[36,837,558],{"class":91},[36,839,50],{"class":42},[36,841,842],{"class":38,"line":449},[36,843,844],{"class":42},"  )}\n",[36,846,847,850,852,855],{"class":38,"line":473},[36,848,849],{"class":80},"  resetKeys",[36,851,85],{"class":84},[36,853,854],{"class":42},"{[userId]}       ",[36,856,857],{"class":65},"\u002F\u002F auto-reset when user switches\n",[36,859,861,864,866,869,872,874,876,879,881,884,887],{"class":38,"line":860},11,[36,862,863],{"class":80},"  onError",[36,865,85],{"class":84},[36,867,868],{"class":42},"{(",[36,870,871],{"class":535},"err",[36,873,332],{"class":42},[36,875,232],{"class":535},[36,877,878],{"class":42},") ",[36,880,395],{"class":84},[36,882,883],{"class":42}," Sentry.",[36,885,886],{"class":80},"captureException",[36,888,889],{"class":42},"(err, { extra: info })}\n",[36,891,893],{"class":38,"line":892},12,[36,894,50],{"class":42},[36,896,898,900,903,906,908],{"class":38,"line":897},13,[36,899,56],{"class":42},[36,901,902],{"class":46},"UserProfile",[36,904,905],{"class":80}," userId",[36,907,85],{"class":84},[36,909,910],{"class":42},"{userId} \u002F>\n",[36,912,914,916,918],{"class":38,"line":913},14,[36,915,146],{"class":42},[36,917,77],{"class":46},[36,919,50],{"class":42},[166,921,923],{"id":922},"useerrorboundary-for-async-errors","useErrorBoundary for Async Errors",[15,925,926,929,930,933],{},[33,927,928],{},"showBoundary(error)"," from ",[33,931,932],{},"useErrorBoundary"," escalates errors that happen outside the render cycle — the one category boundaries miss natively:",[26,935,937],{"className":28,"code":936,"language":30,"meta":31,"style":31},"function UserList() {\n  const { showBoundary } = useErrorBoundary();\n\n  useEffect(() => {\n    fetchUsers()\n      .then(setUsers)\n      .catch(err => showBoundary(err)); \u002F\u002F route async error into boundary system\n  }, []);\n  \u002F\u002F ...\n}\n",[33,938,939,948,969,973,985,993,1004,1027,1032,1037],{"__ignoreMap":31},[36,940,941,943,946],{"class":38,"line":39},[36,942,372],{"class":84},[36,944,945],{"class":80}," UserList",[36,947,378],{"class":42},[36,949,950,952,955,958,961,963,966],{"class":38,"line":53},[36,951,383],{"class":84},[36,953,954],{"class":42}," { ",[36,956,957],{"class":46},"showBoundary",[36,959,960],{"class":42}," } ",[36,962,85],{"class":84},[36,964,965],{"class":80}," useErrorBoundary",[36,967,968],{"class":42},"();\n",[36,970,971],{"class":38,"line":72},[36,972,757],{"emptyLinePlaceholder":756},[36,974,975,978,981,983],{"class":38,"line":102},[36,976,977],{"class":80},"  useEffect",[36,979,980],{"class":42},"(() ",[36,982,395],{"class":84},[36,984,398],{"class":42},[36,986,987,990],{"class":38,"line":119},[36,988,989],{"class":80},"    fetchUsers",[36,991,992],{"class":42},"()\n",[36,994,995,998,1001],{"class":38,"line":129},[36,996,997],{"class":42},"      .",[36,999,1000],{"class":80},"then",[36,1002,1003],{"class":42},"(setUsers)\n",[36,1005,1006,1008,1010,1013,1015,1018,1021,1024],{"class":38,"line":143},[36,1007,997],{"class":42},[36,1009,424],{"class":80},[36,1011,1012],{"class":42},"(",[36,1014,871],{"class":535},[36,1016,1017],{"class":84}," =>",[36,1019,1020],{"class":80}," showBoundary",[36,1022,1023],{"class":42},"(err)); ",[36,1025,1026],{"class":65},"\u002F\u002F route async error into boundary system\n",[36,1028,1029],{"class":38,"line":443},[36,1030,1031],{"class":42},"  }, []);\n",[36,1033,1034],{"class":38,"line":449},[36,1035,1036],{"class":65},"  \u002F\u002F ...\n",[36,1038,1039],{"class":38,"line":473},[36,1040,69],{"class":42},[10,1042,1044],{"id":1043},"error-boundaries-suspense","Error Boundaries + Suspense",[15,1046,1047,1050],{},[33,1048,1049],{},"\u003CSuspense>"," handles loading states (a component suspends by throwing a Promise). Error boundaries handle error states (a component throws an Error). The two compose naturally — wrap Suspense inside an ErrorBoundary to cover both failure modes for the same subtree:",[26,1052,1054],{"className":28,"code":1053,"language":30,"meta":31,"style":31},"\u003CErrorBoundary FallbackComponent={ErrorFallback}>\n  \u003CSuspense fallback={\u003CSpinner \u002F>}>\n    {\u002F* Suspends while loading → shows Spinner *\u002F}\n    {\u002F* If it throws an Error → ErrorBoundary catches it *\u002F}\n    \u003CLazyComponent \u002F>\n  \u003C\u002FSuspense>\n\u003C\u002FErrorBoundary>\n",[33,1055,1056,1070,1089,1099,1108,1118,1126],{"__ignoreMap":31},[36,1057,1058,1060,1062,1065,1067],{"class":38,"line":39},[36,1059,43],{"class":42},[36,1061,77],{"class":46},[36,1063,1064],{"class":80}," FallbackComponent",[36,1066,85],{"class":84},[36,1068,1069],{"class":42},"{ErrorFallback}>\n",[36,1071,1072,1074,1077,1079,1081,1083,1086],{"class":38,"line":53},[36,1073,56],{"class":42},[36,1075,1076],{"class":46},"Suspense",[36,1078,81],{"class":80},[36,1080,85],{"class":84},[36,1082,88],{"class":42},[36,1084,1085],{"class":46},"Spinner",[36,1087,1088],{"class":42}," \u002F>}>\n",[36,1090,1091,1094,1097],{"class":38,"line":72},[36,1092,1093],{"class":42},"    {",[36,1095,1096],{"class":65},"\u002F* Suspends while loading → shows Spinner *\u002F",[36,1098,69],{"class":42},[36,1100,1101,1103,1106],{"class":38,"line":102},[36,1102,1093],{"class":42},[36,1104,1105],{"class":65},"\u002F* If it throws an Error → ErrorBoundary catches it *\u002F",[36,1107,69],{"class":42},[36,1109,1110,1112,1115],{"class":38,"line":119},[36,1111,105],{"class":42},[36,1113,1114],{"class":46},"LazyComponent",[36,1116,1117],{"class":42}," \u002F>\n",[36,1119,1120,1122,1124],{"class":38,"line":129},[36,1121,122],{"class":42},[36,1123,1076],{"class":46},[36,1125,50],{"class":42},[36,1127,1128,1130,1132],{"class":38,"line":143},[36,1129,146],{"class":42},[36,1131,77],{"class":46},[36,1133,50],{"class":42},[15,1135,1136,1137,1139,1140,1142],{},"Every ",[33,1138,1049],{}," boundary should have an ",[33,1141,685],{}," wrapper — Suspense only handles the pending state.",[10,1144,1146],{"id":1145},"development-vs-production-behaviour","Development vs Production Behaviour",[15,1148,1149,1150,1153],{},"In ",[21,1151,1152],{},"development",", React re-throws errors after calling the boundary so the browser DevTools overlay and console both show the full stack trace. You will see the error in the console even when a boundary catches it — this is intentional, not a bug.",[15,1155,1149,1156,1159,1160,1162,1163,1165],{},[21,1157,1158],{},"production",", the error is caught silently. Users see only the fallback; developers see nothing unless logging is configured. This is why ",[33,1161,223],{}," \u002F ",[33,1164,695],{}," is essential — it is your only visibility into boundary-caught errors in production.",[10,1167,1169],{"id":1168},"testing-error-boundaries","Testing Error Boundaries",[15,1171,1172,1173,1176],{},"Render a component that intentionally throws and assert the fallback appears. Suppress ",[33,1174,1175],{},"console.error"," to keep test output clean, but always restore the spy.",[26,1178,1180],{"className":28,"code":1179,"language":30,"meta":31,"style":31},"function Bomb() { throw new Error('test error'); }\n\ntest('renders fallback when child throws', () => {\n  const spy = jest.spyOn(console, 'error').mockImplementation(() => {});\n\n  render(\n    \u003CErrorBoundary fallback={\u003Cp>Boundary caught it\u003C\u002Fp>}>\n      \u003CBomb \u002F>\n    \u003C\u002FErrorBoundary>\n  );\n\n  expect(screen.getByText('Boundary caught it')).toBeInTheDocument();\n  spy.mockRestore();\n});\n",[33,1181,1182,1209,1213,1230,1264,1268,1276,1297,1306,1314,1318,1322,1346,1356],{"__ignoreMap":31},[36,1183,1184,1186,1189,1192,1195,1198,1201,1203,1206],{"class":38,"line":39},[36,1185,372],{"class":84},[36,1187,1188],{"class":80}," Bomb",[36,1190,1191],{"class":42},"() { ",[36,1193,1194],{"class":84},"throw",[36,1196,1197],{"class":84}," new",[36,1199,1200],{"class":80}," Error",[36,1202,1012],{"class":42},[36,1204,1205],{"class":566},"'test error'",[36,1207,1208],{"class":42},"); }\n",[36,1210,1211],{"class":38,"line":53},[36,1212,757],{"emptyLinePlaceholder":756},[36,1214,1215,1218,1220,1223,1226,1228],{"class":38,"line":72},[36,1216,1217],{"class":80},"test",[36,1219,1012],{"class":42},[36,1221,1222],{"class":566},"'renders fallback when child throws'",[36,1224,1225],{"class":42},", () ",[36,1227,395],{"class":84},[36,1229,398],{"class":42},[36,1231,1232,1234,1237,1239,1242,1245,1248,1251,1254,1257,1259,1261],{"class":38,"line":102},[36,1233,383],{"class":84},[36,1235,1236],{"class":46}," spy",[36,1238,389],{"class":84},[36,1240,1241],{"class":42}," jest.",[36,1243,1244],{"class":80},"spyOn",[36,1246,1247],{"class":42},"(console, ",[36,1249,1250],{"class":566},"'error'",[36,1252,1253],{"class":42},").",[36,1255,1256],{"class":80},"mockImplementation",[36,1258,980],{"class":42},[36,1260,395],{"class":84},[36,1262,1263],{"class":42}," {});\n",[36,1265,1266],{"class":38,"line":119},[36,1267,757],{"emptyLinePlaceholder":756},[36,1269,1270,1273],{"class":38,"line":129},[36,1271,1272],{"class":80},"  render",[36,1274,1275],{"class":42},"(\n",[36,1277,1278,1280,1282,1284,1286,1288,1290,1293,1295],{"class":38,"line":143},[36,1279,105],{"class":42},[36,1281,77],{"class":46},[36,1283,81],{"class":80},[36,1285,85],{"class":84},[36,1287,88],{"class":42},[36,1289,15],{"class":91},[36,1291,1292],{"class":42},">Boundary caught it\u003C\u002F",[36,1294,15],{"class":91},[36,1296,99],{"class":42},[36,1298,1299,1301,1304],{"class":38,"line":443},[36,1300,582],{"class":42},[36,1302,1303],{"class":46},"Bomb",[36,1305,1117],{"class":42},[36,1307,1308,1310,1312],{"class":38,"line":449},[36,1309,636],{"class":42},[36,1311,77],{"class":46},[36,1313,50],{"class":42},[36,1315,1316],{"class":38,"line":473},[36,1317,645],{"class":42},[36,1319,1320],{"class":38,"line":860},[36,1321,757],{"emptyLinePlaceholder":756},[36,1323,1324,1327,1330,1333,1335,1338,1341,1344],{"class":38,"line":892},[36,1325,1326],{"class":80},"  expect",[36,1328,1329],{"class":42},"(screen.",[36,1331,1332],{"class":80},"getByText",[36,1334,1012],{"class":42},[36,1336,1337],{"class":566},"'Boundary caught it'",[36,1339,1340],{"class":42},")).",[36,1342,1343],{"class":80},"toBeInTheDocument",[36,1345,968],{"class":42},[36,1347,1348,1351,1354],{"class":38,"line":897},[36,1349,1350],{"class":42},"  spy.",[36,1352,1353],{"class":80},"mockRestore",[36,1355,968],{"class":42},[36,1357,1358],{"class":38,"line":913},[36,1359,1360],{"class":42},"});\n",[10,1362,1364],{"id":1363},"key-takeaways","Key Takeaways",[320,1366,1367,1380,1386,1393,1396,1402,1411,1423,1432],{},[323,1368,1369,1370,1373,1374,1376,1377,1379],{},"Error boundaries are ",[21,1371,1372],{},"class components"," that implement ",[33,1375,169],{}," (render-phase, pure, returns state update) and\u002For ",[33,1378,223],{}," (commit-phase, safe for side effects like Sentry logging).",[323,1381,1382,1383,1385],{},"They catch errors during ",[21,1384,313],{}," — not event handlers, async code, SSR, or their own errors.",[323,1387,1388,1389,1392],{},"Place boundaries at ",[21,1390,1391],{},"three levels",": root (last resort), route (page isolation), widget (feature isolation).",[323,1394,1395],{},"Fallback UI should be simple, actionable, and never expose raw error messages in production.",[323,1397,1398,1399,1401],{},"Use ",[33,1400,302],{}," in production — it eliminates the need to write class components and handles reset logic correctly.",[323,1403,1404,1406,1407,1410],{},[33,1405,702],{}," enables ",[21,1408,1409],{},"data-driven recovery"," — the boundary auto-resets when a resource ID changes.",[323,1412,1413,1415,1416,1418,1419,1422],{},[33,1414,932],{}," + ",[33,1417,957],{}," bridges ",[21,1420,1421],{},"async errors"," into the boundary system.",[323,1424,1425,1426,1428,1429,1431],{},"Wrap every ",[33,1427,1049],{}," in an ",[33,1430,685],{}," — they handle different failure modes and compose naturally.",[323,1433,1434],{},"In development, the DevTools overlay appears even when a boundary catches the error — this is expected; test production behavior with a production build.",[1436,1437,1438],"style",{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}",{"title":31,"searchDepth":53,"depth":53,"links":1440},[1441,1442,1446,1447,1448,1449,1450,1451,1454,1455,1456,1457],{"id":12,"depth":53,"text":13},{"id":156,"depth":53,"text":157,"children":1443},[1444,1445],{"id":168,"depth":72,"text":169},{"id":222,"depth":72,"text":223},{"id":292,"depth":53,"text":293},{"id":306,"depth":53,"text":307},{"id":478,"depth":53,"text":479},{"id":513,"depth":53,"text":514},{"id":659,"depth":53,"text":660},{"id":670,"depth":53,"text":671,"children":1452},[1453],{"id":922,"depth":72,"text":923},{"id":1043,"depth":53,"text":1044},{"id":1145,"depth":53,"text":1146},{"id":1168,"depth":53,"text":1169},{"id":1363,"depth":53,"text":1364},"Master React error boundaries for interviews — componentDidCatch, getDerivedStateFromError, fallback UI, async errors, react-error-boundary library, and Suspense integration.","medium","md","React","react",{},"\u002Fblog\u002Freact-error-boundaries-guide","\u002Freact\u002Fpatterns\u002Ferror-boundaries",{"title":5,"description":1458},"blog\u002Freact-error-boundaries-guide","Error Boundaries","Patterns","patterns","2026-06-24","apkQyibnLJN8zWA5fSSzH70u44Uf_L2JiNRNi6N0IcY",1782244083220]