Skip to content

Commit a742f43

Browse files
fix: address error boundary review feedback
Keep the routeChangeComplete listener in sync when resetOnRouteChange changes at runtime and always detach it on unmount. Use a ContextualErrorFallback for content-level boundaries via a new ContentErrorBoundary wrapper, so a failed content region keeps a local inline recovery UI instead of the full-page global fallback (which remains the app-wide safety net in _app.tsx). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 3ac8da2 commit a742f43

5 files changed

Lines changed: 53 additions & 14 deletions

File tree

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import type { ReactNode } from 'react';
2+
import React from 'react';
3+
4+
import ContextualErrorFallback from './ContextualErrorFallback';
5+
import ErrorBoundary from './ErrorBoundary';
6+
7+
interface IContentErrorBoundaryProps {
8+
// The content subtree to protect.
9+
children: ReactNode;
10+
11+
// Short label describing the failed region, e.g. "article" or "page content".
12+
label?: string;
13+
}
14+
15+
/**
16+
* @description Convenience boundary for page content regions. It renders the
17+
* lightweight ContextualErrorFallback so a crash inside the content keeps its
18+
* local recovery UI without taking over the surrounding layout shell.
19+
* @param {IContentErrorBoundaryProps} props - The content children and an optional label.
20+
*/
21+
export default function ContentErrorBoundary({
22+
children,
23+
label = 'content'
24+
}: IContentErrorBoundaryProps): React.JSX.Element {
25+
return (
26+
<ErrorBoundary
27+
fallback={({ error, errorInfo, reset }) => (
28+
<ContextualErrorFallback error={error} errorInfo={errorInfo} reset={reset} label={label} />
29+
)}
30+
>
31+
{children}
32+
</ErrorBoundary>
33+
);
34+
}

components/error/ErrorBoundary.tsx

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,19 +68,24 @@ export default class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBo
6868

6969
componentDidUpdate(prevProps: ErrorBoundaryProps): void {
7070
const { hasError } = this.state;
71-
const { resetKeys } = this.props;
71+
const { resetKeys, resetOnRouteChange = true } = this.props;
72+
const prevResetOnRouteChange = prevProps.resetOnRouteChange ?? true;
73+
74+
if (resetOnRouteChange !== prevResetOnRouteChange) {
75+
if (resetOnRouteChange) {
76+
Router.events.on('routeChangeComplete', this.reset);
77+
} else {
78+
Router.events.off('routeChangeComplete', this.reset);
79+
}
80+
}
7281

7382
if (hasError && haveResetKeysChanged(prevProps.resetKeys, resetKeys)) {
7483
this.reset();
7584
}
7685
}
7786

7887
componentWillUnmount(): void {
79-
const { resetOnRouteChange = true } = this.props;
80-
81-
if (resetOnRouteChange) {
82-
Router.events.off('routeChangeComplete', this.reset);
83-
}
88+
Router.events.off('routeChangeComplete', this.reset);
8489
}
8590

8691
/**

components/layout/BlogLayout.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import type { IPosts } from '@/types/post';
99
import BlogContext from '../../context/BlogContext';
1010
import AuthorAvatars from '../AuthorAvatars';
1111
import AnnouncementHero from '../campaigns/AnnouncementHero';
12-
import ErrorBoundary from '../error/ErrorBoundary';
12+
import ContentErrorBoundary from '../error/ContentErrorBoundary';
1313
import Head from '../Head';
1414
import TOC from '../TOC';
1515
import Container from './Container';
@@ -92,7 +92,7 @@ export default function BlogLayout({ post, children }: IBlogLayoutProps) {
9292
</HtmlHead>
9393
)}
9494
<img src={post.cover} alt={post.coverCaption} title={post.coverCaption} className='my-6 w-full' />
95-
<ErrorBoundary>{children}</ErrorBoundary>
95+
<ContentErrorBoundary label='article'>{children}</ContentErrorBoundary>
9696
</article>
9797
</main>
9898
</Container>

components/layout/DocsLayout.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import DocsContext from '../../context/DocsContext';
1212
import { getAllPosts } from '../../utils/api';
1313
import Button from '../buttons/Button';
1414
import DocsButton from '../buttons/DocsButton';
15-
import ErrorBoundary from '../error/ErrorBoundary';
15+
import ContentErrorBoundary from '../error/ContentErrorBoundary';
1616
import Feedback from '../Feedback';
1717
import Head from '../Head';
1818
import ArrowRight from '../icons/ArrowRight';
@@ -113,7 +113,7 @@ export default function DocsLayout({ post, navItems = {}, children }: IDocsLayou
113113
{explorerDocMenu && <div className='explorer-menu-wrapper mt-2'>{sidebar}</div>}
114114
</div>
115115
<article>
116-
<ErrorBoundary>{children}</ErrorBoundary>
116+
<ContentErrorBoundary label='content'>{children}</ContentErrorBoundary>
117117
</article>
118118
</div>
119119
);
@@ -203,7 +203,7 @@ export default function DocsLayout({ post, navItems = {}, children }: IDocsLayou
203203
)}
204204
<article className='my-12 overflow-x-auto'>
205205
<Head title={post.title} description={post.excerpt} image={post.cover} />
206-
<ErrorBoundary>{children}</ErrorBoundary>
206+
<ContentErrorBoundary label='content'>{children}</ContentErrorBoundary>
207207
</article>
208208
<div>
209209
<DocsButton post={post} />

components/layout/Layout.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type { IPost, IPosts } from '@/types/post';
66

77
import BlogContext from '../../context/BlogContext';
88
import { getAllPosts, getDocBySlug, getPostBySlug } from '../../utils/api';
9-
import ErrorBoundary from '../error/ErrorBoundary';
9+
import ContentErrorBoundary from '../error/ContentErrorBoundary';
1010
import BlogLayout from './BlogLayout';
1111
import DocsLayout from './DocsLayout';
1212
import GenericPostLayout from './GenericPostLayout';
@@ -58,14 +58,14 @@ export default function Layout({ children }: ILayoutProps): React.JSX.Element {
5858
if (post) {
5959
return (
6060
<GenericPostLayout post={post as unknown as IPosts['blog'][number]}>
61-
<ErrorBoundary>{children}</ErrorBoundary>
61+
<ContentErrorBoundary label='content'>{children}</ContentErrorBoundary>
6262
</GenericPostLayout>
6363
);
6464
}
6565

6666
return (
6767
<div className='min-h-screen bg-white dark:bg-dark-background transition-colors duration-300'>
68-
<ErrorBoundary>{children}</ErrorBoundary>
68+
<ContentErrorBoundary label='page content'>{children}</ContentErrorBoundary>
6969
</div>
7070
);
7171
}

0 commit comments

Comments
 (0)