Skip to content

Commit 4c70f37

Browse files
hotfix(notion): error boundary + null guard in NotionRenderer
react-notion-x crashes (client-side white-screen) on a null or partial recordMap, e.g. when Notion is temporarily unreachable and fetchNotionPage returns null. Guard empty recordMaps and wrap the renderer in an error boundary so a bad lesson degrades to a notice instead of crashing the app. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1 parent 8240b81 commit 4c70f37

1 file changed

Lines changed: 47 additions & 10 deletions

File tree

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"use client";
2-
import { useMemo } from "react";
2+
import { Component, ReactNode, useMemo } from "react";
33
import { NotionRenderer as NotionRendererLib } from "react-notion-x";
44
import { useTheme } from "next-themes";
55
import { useRecoilValue } from "recoil";
@@ -8,6 +8,37 @@ import { isLegacyViewMode } from "@repo/store";
88

99
import CodeBlock from "./CodeBlock";
1010

11+
function UnavailableNotice() {
12+
return (
13+
<div className="flex flex-col items-center justify-center gap-2 py-24 text-center">
14+
<p className="text-lg font-medium">This lesson is temporarily unavailable</p>
15+
<p className="text-sm text-muted-foreground">
16+
We couldn&apos;t load the content right now. Please refresh in a little while.
17+
</p>
18+
</div>
19+
);
20+
}
21+
22+
// react-notion-x throws on a null/partial recordMap. An error boundary ensures a single
23+
// bad lesson degrades to a notice instead of white-screening the whole app.
24+
class NotionErrorBoundary extends Component<{ children: ReactNode }, { hasError: boolean }> {
25+
constructor(props: { children: ReactNode }) {
26+
super(props);
27+
this.state = { hasError: false };
28+
}
29+
static getDerivedStateFromError() {
30+
return { hasError: true };
31+
}
32+
componentDidCatch(error: unknown) {
33+
// eslint-disable-next-line no-console
34+
console.error("[NotionRenderer] render failed:", error);
35+
}
36+
render() {
37+
if (this.state.hasError) return <UnavailableNotice />;
38+
return this.props.children;
39+
}
40+
}
41+
1142
// Week-4-1-647987d9b1894c54ba5c822978377910
1243
export const NotionRenderer = ({ recordMap }: { recordMap: any }) => {
1344
const { resolvedTheme } = useTheme();
@@ -23,15 +54,21 @@ export const NotionRenderer = ({ recordMap }: { recordMap: any }) => {
2354
[]
2455
);
2556

57+
if (!recordMap?.block || Object.keys(recordMap.block).length === 0) {
58+
return <UnavailableNotice />;
59+
}
60+
2661
return (
27-
<NotionRendererLib
28-
bodyClassName="text-base sm:text-lg"
29-
className={isLegacyMode ? "" : "pt-12 dark:!bg-[#0a0a0a]"}
30-
components={components}
31-
darkMode={isDarkMode}
32-
disableHeader
33-
fullPage
34-
recordMap={recordMap}
35-
/>
62+
<NotionErrorBoundary>
63+
<NotionRendererLib
64+
bodyClassName="text-base sm:text-lg"
65+
className={isLegacyMode ? "" : "pt-12 dark:!bg-[#0a0a0a]"}
66+
components={components}
67+
darkMode={isDarkMode}
68+
disableHeader
69+
fullPage
70+
recordMap={recordMap}
71+
/>
72+
</NotionErrorBoundary>
3673
);
3774
};

0 commit comments

Comments
 (0)