Skip to content

Commit b4c5c94

Browse files
talldandesrosj
authored andcommitted
Add error boundaries to widget screens (#33771)
* Add error boundary to edit widgets screen * Add error boundary to customize widgets * Refactor sidebar controls provider to application level so that its state is not lost when re-initializing * Revert "Refactor sidebar controls provider to application level so that its state is not lost when re-initializing" This reverts commit 7d607ff. * Remove rebootability from customize widgets * Remove debug code
1 parent 20eb111 commit b4c5c94

File tree

7 files changed

+168
-21
lines changed

7 files changed

+168
-21
lines changed

packages/customize-widgets/src/components/customize-widgets/index.js

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { SlotFillProvider, Popover } from '@wordpress/components';
77
/**
88
* Internal dependencies
99
*/
10+
import ErrorBoundary from '../error-boundary';
1011
import SidebarBlockEditor from '../sidebar-block-editor';
1112
import FocusControl from '../focus-control';
1213
import SidebarControls from '../sidebar-controls';
@@ -42,13 +43,15 @@ export default function CustomizeWidgets( {
4243
const activeSidebar =
4344
activeSidebarControl &&
4445
createPortal(
45-
<SidebarBlockEditor
46-
key={ activeSidebarControl.id }
47-
blockEditorSettings={ blockEditorSettings }
48-
sidebar={ activeSidebarControl.sidebarAdapter }
49-
inserter={ activeSidebarControl.inserter }
50-
inspector={ activeSidebarControl.inspector }
51-
/>,
46+
<ErrorBoundary>
47+
<SidebarBlockEditor
48+
key={ activeSidebarControl.id }
49+
blockEditorSettings={ blockEditorSettings }
50+
sidebar={ activeSidebarControl.sidebarAdapter }
51+
inserter={ activeSidebarControl.inserter }
52+
inspector={ activeSidebarControl.inspector }
53+
/>
54+
</ErrorBoundary>,
5255
activeSidebarControl.container[ 0 ]
5356
);
5457

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**
2+
* WordPress dependencies
3+
*/
4+
import { Component } from '@wordpress/element';
5+
import { __ } from '@wordpress/i18n';
6+
import { Button } from '@wordpress/components';
7+
import { Warning } from '@wordpress/block-editor';
8+
import { useCopyToClipboard } from '@wordpress/compose';
9+
10+
function CopyButton( { text, children } ) {
11+
const ref = useCopyToClipboard( text );
12+
return (
13+
<Button variant="secondary" ref={ ref }>
14+
{ children }
15+
</Button>
16+
);
17+
}
18+
19+
export default class ErrorBoundary extends Component {
20+
constructor() {
21+
super( ...arguments );
22+
this.state = {
23+
error: null,
24+
};
25+
}
26+
27+
componentDidCatch( error ) {
28+
this.setState( { error } );
29+
}
30+
31+
render() {
32+
const { error } = this.state;
33+
if ( ! error ) {
34+
return this.props.children;
35+
}
36+
37+
return (
38+
<Warning
39+
className="customize-widgets-error-boundary"
40+
actions={ [
41+
<CopyButton key="copy-error" text={ error.stack }>
42+
{ __( 'Copy Error' ) }
43+
</CopyButton>,
44+
] }
45+
>
46+
{ __( 'The editor has encountered an unexpected error.' ) }
47+
</Warning>
48+
);
49+
}
50+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/**
2+
* WordPress dependencies
3+
*/
4+
import { Component } from '@wordpress/element';
5+
import { __ } from '@wordpress/i18n';
6+
import { Button } from '@wordpress/components';
7+
import { Warning } from '@wordpress/block-editor';
8+
import { useCopyToClipboard } from '@wordpress/compose';
9+
10+
function CopyButton( { text, children } ) {
11+
const ref = useCopyToClipboard( text );
12+
return (
13+
<Button variant="secondary" ref={ ref }>
14+
{ children }
15+
</Button>
16+
);
17+
}
18+
19+
export default class ErrorBoundary extends Component {
20+
constructor() {
21+
super( ...arguments );
22+
23+
this.reboot = this.reboot.bind( this );
24+
25+
this.state = {
26+
error: null,
27+
};
28+
}
29+
30+
componentDidCatch( error ) {
31+
this.setState( { error } );
32+
}
33+
34+
reboot() {
35+
this.props.onError();
36+
}
37+
38+
render() {
39+
const { error } = this.state;
40+
if ( ! error ) {
41+
return this.props.children;
42+
}
43+
44+
return (
45+
<Warning
46+
className="edit-widgets-error-boundary"
47+
actions={ [
48+
<Button
49+
key="recovery"
50+
onClick={ this.reboot }
51+
variant="secondary"
52+
>
53+
{ __( 'Attempt Recovery' ) }
54+
</Button>,
55+
<CopyButton key="copy-error" text={ error.stack }>
56+
{ __( 'Copy Error' ) }
57+
</CopyButton>,
58+
] }
59+
>
60+
{ __( 'The editor has encountered an unexpected error.' ) }
61+
</Warning>
62+
);
63+
}
64+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.edit-widgets-error-boundary {
2+
margin: auto;
3+
max-width: 780px;
4+
padding: 20px;
5+
margin-top: 60px;
6+
box-shadow: $shadow-modal;
7+
}

packages/edit-widgets/src/components/layout/index.js

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,27 @@ import { PluginArea } from '@wordpress/plugins';
77
/**
88
* Internal dependencies
99
*/
10+
import ErrorBoundary from '../error-boundary';
1011
import WidgetAreasBlockEditorProvider from '../widget-areas-block-editor-provider';
1112
import Sidebar from '../sidebar';
1213
import Interface from './interface';
1314
import UnsavedChangesWarning from './unsaved-changes-warning';
1415
import WelcomeGuide from '../welcome-guide';
1516

16-
function Layout( { blockEditorSettings } ) {
17+
function Layout( { blockEditorSettings, onError } ) {
1718
return (
18-
<WidgetAreasBlockEditorProvider
19-
blockEditorSettings={ blockEditorSettings }
20-
>
21-
<Interface blockEditorSettings={ blockEditorSettings } />
22-
<Sidebar />
23-
<Popover.Slot />
24-
<PluginArea />
25-
<UnsavedChangesWarning />
26-
<WelcomeGuide />
27-
</WidgetAreasBlockEditorProvider>
19+
<ErrorBoundary onError={ onError }>
20+
<WidgetAreasBlockEditorProvider
21+
blockEditorSettings={ blockEditorSettings }
22+
>
23+
<Interface blockEditorSettings={ blockEditorSettings } />
24+
<Sidebar />
25+
<Popover.Slot />
26+
<PluginArea />
27+
<UnsavedChangesWarning />
28+
<WelcomeGuide />
29+
</WidgetAreasBlockEditorProvider>
30+
</ErrorBoundary>
2831
);
2932
}
3033

packages/edit-widgets/src/index.js

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
unstable__bootstrapServerSideBlockDefinitions, // eslint-disable-line camelcase
77
setFreeformContentHandlerName,
88
} from '@wordpress/blocks';
9-
import { render } from '@wordpress/element';
9+
import { render, unmountComponentAtNode } from '@wordpress/element';
1010
import {
1111
registerCoreBlocks,
1212
__experimentalGetCoreBlocks,
@@ -36,13 +36,32 @@ const disabledBlocks = [
3636
...( ! ALLOW_REUSABLE_BLOCKS && [ 'core/block' ] ),
3737
];
3838

39+
/**
40+
* Reinitializes the editor after the user chooses to reboot the editor after
41+
* an unhandled error occurs, replacing previously mounted editor element using
42+
* an initial state from prior to the crash.
43+
*
44+
* @param {Element} target DOM node in which editor is rendered.
45+
* @param {?Object} settings Editor settings object.
46+
*/
47+
export function reinitializeEditor( target, settings ) {
48+
unmountComponentAtNode( target );
49+
const reboot = reinitializeEditor.bind( null, target, settings );
50+
render(
51+
<Layout blockEditorSettings={ settings } onError={ reboot } />,
52+
target
53+
);
54+
}
55+
3956
/**
4057
* Initializes the block editor in the widgets screen.
4158
*
4259
* @param {string} id ID of the root element to render the screen in.
4360
* @param {Object} settings Block editor settings.
4461
*/
4562
export function initialize( id, settings ) {
63+
const target = document.getElementById( id );
64+
const reboot = reinitializeEditor.bind( null, target, settings );
4665
const coreBlocks = __experimentalGetCoreBlocks().filter( ( block ) => {
4766
return ! (
4867
disabledBlocks.includes( block.name ) ||
@@ -70,8 +89,8 @@ export function initialize( id, settings ) {
7089
// see: https://github.com/WordPress/gutenberg/issues/33097
7190
setFreeformContentHandlerName( 'core/html' );
7291
render(
73-
<Layout blockEditorSettings={ settings } />,
74-
document.getElementById( id )
92+
<Layout blockEditorSettings={ settings } onError={ reboot } />,
93+
target
7594
);
7695
}
7796

packages/edit-widgets/src/style.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
@import "../../interface/src/style.scss";
22

33
@import "./blocks/widget-area/editor.scss";
4+
@import "./components/error-boundary/style.scss";
45
@import "./components/header/style.scss";
56
@import "./components/keyboard-shortcut-help-modal/style.scss";
67
@import "./components/more-menu/style.scss";

0 commit comments

Comments
 (0)