feat(core): Add React ErrorBoundary component + theme default boundaries (#3104)

Co-authored-by: Paden Clayton <paden.clayton@monkedia.com>
Co-authored-by: Josh-Cena <sidachen2003@gmail.com>
Co-authored-by: sebastienlorber <lorber.sebastien@gmail.com>
This commit is contained in:
Paden Clayton 2021-11-04 10:07:32 -05:00 committed by GitHub
parent 4922764095
commit fa6d15b35f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 317 additions and 11 deletions

View file

@ -74,6 +74,14 @@ declare module '@generated/codeTranslations' {
declare module '@theme-original/*';
declare module '@theme/Error' {
export interface Props {
readonly error: Error;
readonly tryAgain: () => void;
}
export default function Error(props: Props): JSX.Element;
}
declare module '@theme/Layout' {
import type {ReactNode} from 'react';
@ -108,6 +116,17 @@ declare module '@docusaurus/constants' {
export const DEFAULT_PLUGIN_ID: 'default';
}
declare module '@docusaurus/ErrorBoundary' {
import type {ReactNode} from 'react';
import ErrorComponent from '@theme/Error';
export interface Props {
readonly fallback?: typeof ErrorComponent;
readonly children: ReactNode;
}
export default function ErrorBoundary(props: Props): JSX.Element;
}
declare module '@docusaurus/Head' {
import type {HelmetProps} from 'react-helmet';
import type {ReactNode} from 'react';

View file

@ -4,6 +4,8 @@
"theme.CodeBlock.copied": "تم النسخ",
"theme.CodeBlock.copy": "نسخ",
"theme.CodeBlock.copyButtonAriaLabel": "نسخ الرمز إلى الحافظة",
"theme.ErrorPageContent.title": "This page crashed.",
"theme.ErrorPageContent.tryAgain": "Try again",
"theme.NotFound.p1": "لم نتمكن من العثور على ما كنت تبحث عنه.",
"theme.NotFound.p2": "يرجى الاتصال بمالك الموقع الذي ربطك بعنوان URL الأصلي وإخباره بأن الارتباط الخاص به معطل.",
"theme.NotFound.title": "الصفحة غير موجودة",

View file

@ -9,6 +9,10 @@
"theme.CodeBlock.copy___DESCRIPTION": "The copy button label on code blocks",
"theme.CodeBlock.copyButtonAriaLabel": "Copy code to clipboard",
"theme.CodeBlock.copyButtonAriaLabel___DESCRIPTION": "The ARIA label for copy code blocks button",
"theme.ErrorPageContent.title": "This page crashed.",
"theme.ErrorPageContent.title___DESCRIPTION": "The title of the fallback page when the page crashed",
"theme.ErrorPageContent.tryAgain": "Try again",
"theme.ErrorPageContent.tryAgain___DESCRIPTION": "The label of the button to try again when the page crashed",
"theme.NotFound.p1": "We could not find what you were looking for.",
"theme.NotFound.p1___DESCRIPTION": "The first paragraph of the 404 page",
"theme.NotFound.p2": "Please contact the owner of the site that linked you to the original URL and let them know their link is broken.",

View file

@ -4,6 +4,8 @@
"theme.CodeBlock.copied": "কপিড",
"theme.CodeBlock.copy": "কপি",
"theme.CodeBlock.copyButtonAriaLabel": "ক্লিপবোর্ডে কোড কপি করুন",
"theme.ErrorPageContent.title": "This page crashed.",
"theme.ErrorPageContent.tryAgain": "Try again",
"theme.NotFound.p1": "আপনি যা খুঁজছিলেন তা আমরা খুঁজে পাইনি।",
"theme.NotFound.p2": "দয়া করে সাইটের মালিকের সাথে যোগাযোগ করুন যা আপনাকে মূল URL এর সাথে যুক্ত করেছে এবং তাদের লিঙ্কটি ভাঙ্গা রয়েছে তা তাদের জানান।",
"theme.NotFound.title": "পেজটি খুঁজে পাওয়া যায়নি",

View file

@ -4,6 +4,8 @@
"theme.CodeBlock.copied": "Zkopírováno",
"theme.CodeBlock.copy": "Zkopírovat",
"theme.CodeBlock.copyButtonAriaLabel": "Zkopírovat kód do schránky",
"theme.ErrorPageContent.title": "This page crashed.",
"theme.ErrorPageContent.tryAgain": "Try again",
"theme.NotFound.p1": "Nepodařilo se nám najít co jste hledal(a).",
"theme.NotFound.p2": "Kontaktujte prosím vlastníka webu, který vás odkázal na původní URL a upozorněte ho, že jejich odkaz nefunguje.",
"theme.NotFound.title": "Stránka nenalezena",

View file

@ -4,6 +4,8 @@
"theme.CodeBlock.copied": "Kopieret",
"theme.CodeBlock.copy": "Kopier",
"theme.CodeBlock.copyButtonAriaLabel": "Kopier kode til udklipsholder",
"theme.ErrorPageContent.title": "This page crashed.",
"theme.ErrorPageContent.tryAgain": "Try again",
"theme.NotFound.p1": "Vi kunne ikke finde det, du søgte.",
"theme.NotFound.p2": "Venligst kontakt ejeren til webstedet, som førte dig frem denne URL, og informer dem om at linket ikke virker.",
"theme.NotFound.title": "Siden blev ikke fundet",

View file

@ -4,6 +4,8 @@
"theme.CodeBlock.copied": "Kopiert",
"theme.CodeBlock.copy": "Kopieren",
"theme.CodeBlock.copyButtonAriaLabel": "In die Zwischenablage kopieren",
"theme.ErrorPageContent.title": "This page crashed.",
"theme.ErrorPageContent.tryAgain": "Try again",
"theme.NotFound.p1": "Wir konnten nicht finden, wonach Sie gesucht haben.",
"theme.NotFound.p2": "Bitte kontaktieren Sie den Besitzer der Seite, die Sie mit der ursprünglichen URL verlinkt hat, und teilen Sie ihm mit, dass der Link nicht mehr funktioniert.",
"theme.NotFound.title": "Seite nicht gefunden",

View file

@ -4,6 +4,8 @@
"theme.CodeBlock.copied": "Copiado",
"theme.CodeBlock.copy": "Copiar",
"theme.CodeBlock.copyButtonAriaLabel": "Copiar código al portapapeles",
"theme.ErrorPageContent.title": "This page crashed.",
"theme.ErrorPageContent.tryAgain": "Try again",
"theme.NotFound.p1": "No pudimos encontrar lo que buscaba.",
"theme.NotFound.p2": "Comuníquese con el dueño del sitio que lo vinculó a la URL original y hágale saber que su vínculo está roto.",
"theme.NotFound.title": "Página No Encontrada",

View file

@ -4,6 +4,8 @@
"theme.CodeBlock.copied": "کپی شد",
"theme.CodeBlock.copy": "کپی",
"theme.CodeBlock.copyButtonAriaLabel": "کپی به کلیپ بورد",
"theme.ErrorPageContent.title": "This page crashed.",
"theme.ErrorPageContent.tryAgain": "Try again",
"theme.NotFound.p1": "صفحه ای که دنبال آن بودید پیدا نشد.",
"theme.NotFound.p2": "لطفا با صاحب وبسایت تماس بگیرید و ایشان را از مشکل پیش آمده مطلع کنید.",
"theme.NotFound.title": "صفحه ای که دنبال آن بودید پیدا نشد.",

View file

@ -4,6 +4,8 @@
"theme.CodeBlock.copied": "Kinopya",
"theme.CodeBlock.copy": "Kopyahin",
"theme.CodeBlock.copyButtonAriaLabel": "Kopyahin ang code sa clipboard",
"theme.ErrorPageContent.title": "This page crashed.",
"theme.ErrorPageContent.tryAgain": "Try again",
"theme.NotFound.p1": "Hindi namin mahanap ang iyong hinananap.",
"theme.NotFound.p2": "Mangyaring makipag-ugnayan sa may-ari ng site na nag-link sa iyo sa orihinal na URL at sabihin sa kanila na ang kanilang link ay putol.",
"theme.NotFound.title": "Hindi Nahanap ang Pahina",

View file

@ -4,6 +4,8 @@
"theme.CodeBlock.copied": "Copié",
"theme.CodeBlock.copy": "Copier",
"theme.CodeBlock.copyButtonAriaLabel": "Copier le code",
"theme.ErrorPageContent.title": "This page crashed.",
"theme.ErrorPageContent.tryAgain": "Try again",
"theme.NotFound.p1": "Nous n'avons pas trouvé ce que vous recherchez.",
"theme.NotFound.p2": "Veuillez contacter le propriétaire du site qui vous a lié à l'URL d'origine et leur faire savoir que leur lien est cassé.",
"theme.NotFound.title": "Page introuvable",

View file

@ -4,6 +4,8 @@
"theme.CodeBlock.copied": "הועתק",
"theme.CodeBlock.copy": "העתק",
"theme.CodeBlock.copyButtonAriaLabel": "העתק קוד ללוח העריכה",
"theme.ErrorPageContent.title": "This page crashed.",
"theme.ErrorPageContent.tryAgain": "Try again",
"theme.NotFound.p1": "אנחנו לא מוצאים את מה שאתה מנסה לחפש.",
"theme.NotFound.p2": "הקישור אינו תקין, אנא פנה למנהל האתר ממנו קיבלת קישור זה.",
"theme.NotFound.title": "דף לא נמצא",

View file

@ -4,6 +4,8 @@
"theme.CodeBlock.copied": "कॉपीड",
"theme.CodeBlock.copy": "कॉपी",
"theme.CodeBlock.copyButtonAriaLabel": "क्लिपबोर्ड पर कोड कॉपी करें",
"theme.ErrorPageContent.title": "This page crashed.",
"theme.ErrorPageContent.tryAgain": "Try again",
"theme.NotFound.p1": "हमें वह नहीं मिला, जिसकी आपको तलाश थी।",
"theme.NotFound.p2": "कृपया उस साइट के मालिक से संपर्क करें जिसने आपको मूल URL से जोड़ा है और उन्हें बताएं कि उनका लिंक टूट गया है।",
"theme.NotFound.title": "पेज नहीं मिला",

View file

@ -4,6 +4,8 @@
"theme.CodeBlock.copied": "コピーしました",
"theme.CodeBlock.copy": "コピー",
"theme.CodeBlock.copyButtonAriaLabel": "クリップボードにコードをコピー",
"theme.ErrorPageContent.title": "This page crashed.",
"theme.ErrorPageContent.tryAgain": "Try again",
"theme.NotFound.p1": "お探しのページが見つかりませんでした。",
"theme.NotFound.p2": "このページにリンクしているサイトの所有者に連絡をしてリンクが壊れていることを伝えてください。",
"theme.NotFound.title": "ページが見つかりません",

View file

@ -4,6 +4,8 @@
"theme.CodeBlock.copied": "복사했습니다",
"theme.CodeBlock.copy": "복사",
"theme.CodeBlock.copyButtonAriaLabel": "클립보드에 코드 복사",
"theme.ErrorPageContent.title": "This page crashed.",
"theme.ErrorPageContent.tryAgain": "Try again",
"theme.NotFound.p1": "원하는 페이지를 찾을 수 없습니다.",
"theme.NotFound.p2": "사이트 관리자에게 링크가 깨진 것을 알려주세요.",
"theme.NotFound.title": "페이지를 찾을 수 없습니다.",

View file

@ -4,6 +4,8 @@
"theme.CodeBlock.copied": "Skopiowano!",
"theme.CodeBlock.copy": "Kopiuj",
"theme.CodeBlock.copyButtonAriaLabel": "Kopiuj do schowka",
"theme.ErrorPageContent.title": "This page crashed.",
"theme.ErrorPageContent.tryAgain": "Try again",
"theme.NotFound.p1": "Nie mogliśmy znaleźć strony której szukasz.",
"theme.NotFound.p2": "Proszę skontaktuj się z właścielem strony, z której link doprowadził Cię tutaj i poinformuj ich, że odnośnik jest nieprawidłowy.",
"theme.NotFound.title": "Strona nie została znaleziona",

View file

@ -4,6 +4,8 @@
"theme.CodeBlock.copied": "Copiado",
"theme.CodeBlock.copy": "Copiar",
"theme.CodeBlock.copyButtonAriaLabel": "Copiar código para a área de transferência",
"theme.ErrorPageContent.title": "This page crashed.",
"theme.ErrorPageContent.tryAgain": "Try again",
"theme.NotFound.p1": "Não foi possível encontrar o que você está procurando.",
"theme.NotFound.p2": "Entre em contato com o proprietário do site que lhe trouxe para cá e lhe informe que o link está quebrado.",
"theme.NotFound.title": "Página não encontrada",

View file

@ -4,6 +4,8 @@
"theme.CodeBlock.copied": "Copiado",
"theme.CodeBlock.copy": "Copiar",
"theme.CodeBlock.copyButtonAriaLabel": "Copiar código para a área de transferência",
"theme.ErrorPageContent.title": "This page crashed.",
"theme.ErrorPageContent.tryAgain": "Try again",
"theme.NotFound.p1": "Não foi possível encontrar o que procura.",
"theme.NotFound.p2": "Por favor, contacte o proprietário do site que o trouxe aqui e informe-lhe que o link está partido.",
"theme.NotFound.title": "Página não encontrada",

View file

@ -4,6 +4,8 @@
"theme.CodeBlock.copied": "Скопировано",
"theme.CodeBlock.copy": "Скопировать",
"theme.CodeBlock.copyButtonAriaLabel": "Скопировать в буфер обмена",
"theme.ErrorPageContent.title": "This page crashed.",
"theme.ErrorPageContent.tryAgain": "Try again",
"theme.NotFound.p1": "К сожалению, мы не смогли найти запрашиваемую вами страницу.",
"theme.NotFound.p2": "Пожалуйста, обратитесь к владельцу сайта, с которого вы перешли на эту ссылку, чтобы сообщить ему, что ссылка не работает.",
"theme.NotFound.title": "Страница не найдена",

View file

@ -4,6 +4,8 @@
"theme.CodeBlock.copied": "Копирано",
"theme.CodeBlock.copy": "Копирај",
"theme.CodeBlock.copyButtonAriaLabel": "Копирај код у меморију",
"theme.ErrorPageContent.title": "This page crashed.",
"theme.ErrorPageContent.tryAgain": "Try again",
"theme.NotFound.p1": "Тражени резултат не постоји.",
"theme.NotFound.p2": "Молимо вас да контактирате власника сајта који вас је упутио овде и обавестите га да је њихова веза нетачна.",
"theme.NotFound.title": "Страница није пронађена",

View file

@ -4,6 +4,8 @@
"theme.CodeBlock.copied": "Kopyalandı",
"theme.CodeBlock.copy": "Kopyala",
"theme.CodeBlock.copyButtonAriaLabel": "Kodu panoya kopyala",
"theme.ErrorPageContent.title": "This page crashed.",
"theme.ErrorPageContent.tryAgain": "Try again",
"theme.NotFound.p1": "Aradığınız şeyi bulamadık.",
"theme.NotFound.p2": "Lütfen sizi orijinal URL'ye yönlendiren sitenin sahibiyle iletişime geçin ve bağlantısının bozuk olduğunu bildirin.",
"theme.NotFound.title": "Sayfa Bulunamadı",

View file

@ -4,6 +4,8 @@
"theme.CodeBlock.copied": "Đã sao chép",
"theme.CodeBlock.copy": "Sao chép",
"theme.CodeBlock.copyButtonAriaLabel": "Sao chép code vào bộ nhớ tạm",
"theme.ErrorPageContent.title": "This page crashed.",
"theme.ErrorPageContent.tryAgain": "Try again",
"theme.NotFound.p1": "Chúng tôi không thể tìm thấy những gì bạn đang tìm kiếm.",
"theme.NotFound.p2": "Vui lòng liên hệ với trang web đã dẫn bạn tới đây và thông báo cho họ biết rằng đường dẫn này bị hỏng.",
"theme.NotFound.title": "Không tìm thấy trang",

View file

@ -4,6 +4,8 @@
"theme.CodeBlock.copied": "复制成功",
"theme.CodeBlock.copy": "复制",
"theme.CodeBlock.copyButtonAriaLabel": "复制代码到剪贴板",
"theme.ErrorPageContent.title": "页面已崩溃。",
"theme.ErrorPageContent.tryAgain": "重试",
"theme.NotFound.p1": "我们找不到您要找的页面。",
"theme.NotFound.p2": "请联系原始链接来源网站的所有者,并告知他们链接已损坏。",
"theme.NotFound.title": "找不到页面",

View file

@ -4,6 +4,8 @@
"theme.CodeBlock.copied": "複製成功",
"theme.CodeBlock.copy": "複製",
"theme.CodeBlock.copyButtonAriaLabel": "複製代碼至剪貼簿",
"theme.ErrorPageContent.title": "頁面已崩潰。",
"theme.ErrorPageContent.tryAgain": "重試",
"theme.NotFound.p1": "我們找不到您要找的頁面。",
"theme.NotFound.p2": "請聯絡原始連結來源網站的所有者,並通知他們連結已毀損。",
"theme.NotFound.title": "找不到頁面",

View file

@ -0,0 +1,41 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import React from 'react';
import Translate from '@docusaurus/Translate';
import type {Props} from '@theme/Error';
export default function ErrorPageContent({
error,
tryAgain,
}: Props): JSX.Element {
return (
<main className="container margin-vert--xl">
<div className="row">
<div className="col col--6 col--offset-3">
<h1 className="hero__title">
<Translate
id="theme.ErrorPageContent.title"
description="The title of the fallback page when the page crashed">
This page crashed.
</Translate>
</h1>
<p>{error.message}</p>
<div>
<button type="button" onClick={tryAgain}>
<Translate
id="theme.ErrorPageContent.tryAgain"
description="The label of the button to try again when the page crashed">
Try again
</Translate>
</button>
</div>
</div>
</div>
</main>
);
}

View file

@ -7,6 +7,7 @@
import React from 'react';
import clsx from 'clsx';
import ErrorBoundary from '@docusaurus/ErrorBoundary';
import SkipToContent from '@theme/SkipToContent';
import AnnouncementBar from '@theme/AnnouncementBar';
import Navbar from '@theme/Navbar';
@ -16,6 +17,7 @@ import LayoutHead from '@theme/LayoutHead';
import type {Props} from '@theme/Layout';
import useKeyboardNavigation from '@theme/hooks/useKeyboardNavigation';
import {ThemeClassNames} from '@docusaurus/theme-common';
import ErrorPageContent from '@theme/ErrorPageContent';
import './styles.css';
function Layout(props: Props): JSX.Element {
@ -39,7 +41,7 @@ function Layout(props: Props): JSX.Element {
wrapperClassName,
pageClassName,
)}>
{children}
<ErrorBoundary fallback={ErrorPageContent}>{children}</ErrorBoundary>
</div>
{!noFooter && <Footer />}

View file

@ -173,6 +173,13 @@ declare module '@theme/EditThisPage' {
export default EditThisPage;
}
declare module '@theme/ErrorPageContent' {
import ErrorComponent from '@theme/Error';
const ErrorPageContent: typeof ErrorComponent;
export default ErrorPageContent;
}
declare module '@theme/Footer' {
const Footer: () => JSX.Element | null;
export default Footer;

View file

@ -11,24 +11,28 @@ import routes from '@generated/routes';
import renderRoutes from './exports/renderRoutes';
import {BrowserContextProvider} from './exports/browserContext';
import {DocusaurusContextProvider} from './exports/docusaurusContext';
import ErrorBoundary from '@docusaurus/ErrorBoundary';
import PendingNavigation from './PendingNavigation';
import BaseUrlIssueBanner from './baseUrlIssueBanner/BaseUrlIssueBanner';
import Root from '@theme/Root';
import Error from '@theme/Error';
import './client-lifecycles-dispatcher';
function App(): JSX.Element {
return (
<DocusaurusContextProvider>
<BrowserContextProvider>
<Root>
<BaseUrlIssueBanner />
<PendingNavigation routes={routes} delay={1000}>
{renderRoutes(routes)}
</PendingNavigation>
</Root>
</BrowserContextProvider>
</DocusaurusContextProvider>
<ErrorBoundary fallback={Error}>
<DocusaurusContextProvider>
<BrowserContextProvider>
<Root>
<BaseUrlIssueBanner />
<PendingNavigation routes={routes} delay={1000}>
{renderRoutes(routes)}
</PendingNavigation>
</Root>
</BrowserContextProvider>
</DocusaurusContextProvider>
</ErrorBoundary>
);
}

View file

@ -0,0 +1,47 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import React, {ReactNode} from 'react';
import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
import type {Props} from '@docusaurus/ErrorBoundary';
import DefaultFallback from '@theme/Error';
interface State {
error: Error | null;
}
class ErrorBoundary extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {error: null};
}
componentDidCatch(error: Error): void {
// Catch errors in any components below and re-render with error message
if (ExecutionEnvironment.canUseDOM) {
this.setState({error});
}
}
render(): ReactNode {
const {children} = this.props;
const {error} = this.state;
if (error) {
const fallback = this.props.fallback ?? DefaultFallback;
return fallback({
error,
tryAgain: () => this.setState({error: null}),
});
}
return children;
}
}
export default ErrorBoundary;

View file

@ -0,0 +1,47 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import React from 'react';
import Layout from '@theme/Layout';
import ErrorBoundary from '@docusaurus/ErrorBoundary';
function ErrorDisplay({error, tryAgain}) {
return (
<div
style={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
height: '50vh',
width: '100%',
fontSize: '20px',
}}>
<h1>This page crashed.</h1>
<p>{error.message}</p>
<button type="button" onClick={tryAgain}>
Try again
</button>
</div>
);
}
function Error({error, tryAgain}) {
// We wrap the error in its own error boundary because the layout can actually throw too...
// Only the ErrorDisplay component is simple enough to be considered safe to never throw
return (
<ErrorBoundary
// Note: we display the original error here, not the error that we captured in this extra error boundary
fallback={() => <ErrorDisplay error={error} tryAgain={tryAgain} />}>
<Layout title="Page Error">
<ErrorDisplay error={error} tryAgain={tryAgain} />
</Layout>
</ErrorBoundary>
);
}
export default Error;

View file

@ -4,6 +4,7 @@ exports[`base webpack config should create webpack aliases 1`] = `
Object {
"@docusaurus/BrowserOnly": "../../../../client/exports/BrowserOnly.tsx",
"@docusaurus/ComponentCreator": "../../../../client/exports/ComponentCreator.tsx",
"@docusaurus/ErrorBoundary": "../../../../client/exports/ErrorBoundary.tsx",
"@docusaurus/ExecutionEnvironment": "../../../../client/exports/ExecutionEnvironment.ts",
"@docusaurus/Head": "../../../../client/exports/Head.tsx",
"@docusaurus/Interpolate": "../../../../client/exports/Interpolate.tsx",
@ -23,6 +24,7 @@ Object {
"@generated": "../../../../../../..",
"@site": "",
"@theme-init/PluginThemeComponentOverridden": "pluginThemeFolder/PluginThemeComponentOverridden.js",
"@theme-original/Error": "../../../../client/theme-fallback/Error/index.js",
"@theme-original/Layout": "../../../../client/theme-fallback/Layout/index.js",
"@theme-original/Loading": "../../../../client/theme-fallback/Loading/index.js",
"@theme-original/NotFound": "../../../../client/theme-fallback/NotFound/index.js",
@ -30,6 +32,7 @@ Object {
"@theme-original/PluginThemeComponentOverridden": "pluginThemeFolder/PluginThemeComponentOverridden.js",
"@theme-original/Root": "../../../../client/theme-fallback/Root/index.js",
"@theme-original/subfolder/PluginThemeComponent2": "pluginThemeFolder/subfolder/PluginThemeComponent2.js",
"@theme/Error": "../../../../client/theme-fallback/Error/index.js",
"@theme/Layout": "../../../../client/theme-fallback/Layout/index.js",
"@theme/Loading": "../../../../client/theme-fallback/Loading/index.js",
"@theme/NotFound": "../../../../client/theme-fallback/NotFound/index.js",
@ -46,6 +49,7 @@ exports[`getDocusaurusAliases() return appropriate webpack aliases 1`] = `
Object {
"@docusaurus/BrowserOnly": "../../client/exports/BrowserOnly.tsx",
"@docusaurus/ComponentCreator": "../../client/exports/ComponentCreator.tsx",
"@docusaurus/ErrorBoundary": "../../client/exports/ErrorBoundary.tsx",
"@docusaurus/ExecutionEnvironment": "../../client/exports/ExecutionEnvironment.ts",
"@docusaurus/Head": "../../client/exports/Head.tsx",
"@docusaurus/Interpolate": "../../client/exports/Interpolate.tsx",

View file

@ -0,0 +1,22 @@
import React from 'react';
import Layout from '@theme/Layout';
import ErrorBoundaryTestButton from '@site/src/components/ErrorBoundaryTestButton';
export default function ErrorBoundaryTests() {
return (
<>
<ErrorBoundaryTestButton>Crash outside layout</ErrorBoundaryTestButton>
<Layout>
<main className="container margin-vert--xl">
<h1>Error boundary tests</h1>
<div>
<ErrorBoundaryTestButton>
Crash inside layout
</ErrorBoundaryTestButton>
</div>
</main>
</Layout>
</>
);
}

View file

@ -8,6 +8,55 @@ Docusaurus provides some APIs on the clients that can be helpful to you when bui
## Components {#components}
### `<ErrorBoundary />` {#errorboundary}
This component creates a [React error boundary](https://reactjs.org/docs/error-boundaries.html).
Use it to wrap components that might throw, and display a fallback when that happens instead of crashing the whole app.
```jsx
import React from 'react';
import ErrorBoundary from '@docusaurus/ErrorBoundary';
const SafeComponent = () => (
<ErrorBoundary
fallback={({error, tryAgain}) => (
<div>
<p>This component crashed because of error: {error.message}.</p>
<button onClick={tryAgain}>Try Again!</button>
</div>
)}>
<SomeDangerousComponentThatMayThrow />
</ErrorBoundary>
);
```
```mdx-code-block
import ErrorBoundaryTestButton from "@site/src/components/ErrorBoundaryTestButton"
```
:::tip
To see it in action, click here: <ErrorBoundaryTestButton/>
:::
:::info
Docusaurus uses this component to catch errors within the theme's layout, and also within the entire app.
:::
:::note
This component doesn't catch build-time errors, and only protects against client-side render errors that can happen when using stateful React components.
:::
#### Props {#errorboundary-props}
- `fallback`: an optional callback returning a JSX element. It will receive two props: `error`, the error that was caught, and `tryAgain`, a function (`() => void`) callback to reset the error in the component and try rendering it again.
### `<Head/>` {#head}
This reusable React component will manage all of your changes to the document head. It takes plain HTML tags and outputs plain HTML tags and is beginner-friendly. It is a wrapper around [React Helmet](https://github.com/nfl/react-helmet).

View file

@ -0,0 +1,16 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import React, {useState} from 'react';
export default function ErrorBoundaryTestButton({children = 'Boom!'}) {
const [state, setState] = useState(false);
if (state) {
throw new Error('Boom!');
}
return <button onClick={() => setState(true)}>{children}</button>;
}