mirror of
https://github.com/facebook/docusaurus.git
synced 2025-07-29 14:38:50 +02:00
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:
parent
4922764095
commit
fa6d15b35f
34 changed files with 317 additions and 11 deletions
|
@ -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';
|
||||
|
|
|
@ -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": "الصفحة غير موجودة",
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -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": "পেজটি খুঁজে পাওয়া যায়নি",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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": "صفحه ای که دنبال آن بودید پیدا نشد.",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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": "דף לא נמצא",
|
||||
|
|
|
@ -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": "पेज नहीं मिला",
|
||||
|
|
|
@ -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": "ページが見つかりません",
|
||||
|
|
|
@ -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": "페이지를 찾을 수 없습니다.",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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": "Страница не найдена",
|
||||
|
|
|
@ -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": "Страница није пронађена",
|
||||
|
|
|
@ -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ı",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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": "找不到页面",
|
||||
|
|
|
@ -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": "找不到頁面",
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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 />}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
47
packages/docusaurus/src/client/exports/ErrorBoundary.tsx
Normal file
47
packages/docusaurus/src/client/exports/ErrorBoundary.tsx
Normal 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;
|
47
packages/docusaurus/src/client/theme-fallback/Error/index.js
Normal file
47
packages/docusaurus/src/client/theme-fallback/Error/index.js
Normal 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;
|
|
@ -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",
|
||||
|
|
22
website/_dogfooding/_pages tests/error-boundary-tests.js
Normal file
22
website/_dogfooding/_pages tests/error-boundary-tests.js
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -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).
|
||||
|
|
16
website/src/components/ErrorBoundaryTestButton/index.tsx
Normal file
16
website/src/components/ErrorBoundaryTestButton/index.tsx
Normal 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>;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue