mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-24 16:02:55 +02:00
Add error boundary to save Slate from a crash on corrupt DOM (#2792)
* Working version of restore dom * Fix linting errors * Add button to corrupt DOM * Added error boundary that fixes DOM on render error * Fix linting errors * Fix debug output for componentDidCatch * Improve example by adding a separate restoreDOM button * Remove key change from error boundary which is not necessary * Fix linting error
This commit is contained in:
@@ -3,7 +3,7 @@ import { Value } from 'slate'
|
|||||||
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import initialValue from './value.json'
|
import initialValue from './value.json'
|
||||||
import { Button, Icon, Toolbar } from '../components'
|
import { Button, EditorValue, Icon, Instruction, Toolbar } from '../components'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Restore DOM example.
|
* The Restore DOM example.
|
||||||
@@ -31,7 +31,7 @@ class RestoreDOMExample extends React.Component {
|
|||||||
|
|
||||||
state = {
|
state = {
|
||||||
value: Value.fromJSON(initialValue),
|
value: Value.fromJSON(initialValue),
|
||||||
bgcolor: '#ffffff',
|
bgcolor: '#ffeecc',
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -53,12 +53,25 @@ class RestoreDOMExample extends React.Component {
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
<Instruction>
|
||||||
|
<ol>
|
||||||
|
<li>
|
||||||
|
Click a brush to change color in state. Use refresh button to
|
||||||
|
`restoreDOM` which renders changes.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Press `!` button to corrupt DOM by removing `bold`. Backspace from
|
||||||
|
start of `text` 5 times. Console will show error but Slate will
|
||||||
|
recover by restoring DOM.
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</Instruction>
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
{this.renderHighlightButton('#ffffff')}
|
|
||||||
{this.renderHighlightButton('#ffeecc')}
|
{this.renderHighlightButton('#ffeecc')}
|
||||||
{this.renderHighlightButton('#ffffcc')}
|
{this.renderHighlightButton('#ffffcc')}
|
||||||
{this.renderHighlightButton('#ccffcc')}
|
{this.renderHighlightButton('#ccffcc')}
|
||||||
{this.renderHighlightButton('#ccffff')}
|
{this.renderCorruptButton()}
|
||||||
|
{this.renderRestoreButton()}
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
<Editor
|
<Editor
|
||||||
spellCheck
|
spellCheck
|
||||||
@@ -68,7 +81,9 @@ class RestoreDOMExample extends React.Component {
|
|||||||
value={this.state.value}
|
value={this.state.value}
|
||||||
onChange={this.onChange}
|
onChange={this.onChange}
|
||||||
renderBlock={this.renderBlock}
|
renderBlock={this.renderBlock}
|
||||||
|
renderMark={this.renderMark}
|
||||||
/>
|
/>
|
||||||
|
<EditorValue value={this.state.value} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -93,6 +108,48 @@ class RestoreDOMExample extends React.Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render restoreDOM button
|
||||||
|
*/
|
||||||
|
|
||||||
|
renderRestoreButton = () => {
|
||||||
|
const { editor } = this
|
||||||
|
|
||||||
|
function restoreDOM() {
|
||||||
|
editor.restoreDOM()
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button onMouseDown={restoreDOM}>
|
||||||
|
<Icon>refresh</Icon>
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a button to corrupt the DOM
|
||||||
|
*
|
||||||
|
*@return {Element}
|
||||||
|
*/
|
||||||
|
|
||||||
|
renderCorruptButton = () => {
|
||||||
|
/**
|
||||||
|
* Corrupt the DOM by forcibly deleting the first instance we can find
|
||||||
|
* of the `bold` text in the DOM.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function corrupt() {
|
||||||
|
const boldEl = window.document.querySelector('[data-bold]')
|
||||||
|
const el = boldEl.closest('[data-slate-object="text"]')
|
||||||
|
el.parentNode.removeChild(el)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Button onMouseDown={corrupt}>
|
||||||
|
<Icon>error_outline</Icon>
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Highlight every block with a given background color
|
* Highlight every block with a given background color
|
||||||
*
|
*
|
||||||
@@ -100,9 +157,7 @@ class RestoreDOMExample extends React.Component {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
onClickHighlight = bgcolor => {
|
onClickHighlight = bgcolor => {
|
||||||
const { editor } = this
|
|
||||||
this.setState({ bgcolor })
|
this.setState({ bgcolor })
|
||||||
editor.restoreDOM()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -128,6 +183,29 @@ class RestoreDOMExample extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a Slate mark.
|
||||||
|
*
|
||||||
|
* @param {Object} props
|
||||||
|
* @return {Element}
|
||||||
|
*/
|
||||||
|
|
||||||
|
renderMark = (props, editor, next) => {
|
||||||
|
const { children, mark, attributes } = props
|
||||||
|
|
||||||
|
switch (mark.type) {
|
||||||
|
case 'bold':
|
||||||
|
// Added `data-bold` so we can find bold text with `querySelector`
|
||||||
|
return (
|
||||||
|
<strong {...attributes} data-bold>
|
||||||
|
{children}
|
||||||
|
</strong>
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On change, save the new `value`.
|
* On change, save the new `value`.
|
||||||
*
|
*
|
||||||
|
@@ -9,7 +9,16 @@
|
|||||||
"nodes": [
|
"nodes": [
|
||||||
{
|
{
|
||||||
"object": "text",
|
"object": "text",
|
||||||
"text": "First block of text"
|
"text": "First block with "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "text",
|
||||||
|
"text": "bold",
|
||||||
|
"marks": [{ "type": "bold" }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "text",
|
||||||
|
"text": " text in it"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -19,7 +28,16 @@
|
|||||||
"nodes": [
|
"nodes": [
|
||||||
{
|
{
|
||||||
"object": "text",
|
"object": "text",
|
||||||
"text": "Second block of text"
|
"text": "Second block with "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "text",
|
||||||
|
"text": "bold",
|
||||||
|
"marks": [{ "type": "bold" }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "text",
|
||||||
|
"text": " text in it"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -29,7 +47,16 @@
|
|||||||
"nodes": [
|
"nodes": [
|
||||||
{
|
{
|
||||||
"object": "text",
|
"object": "text",
|
||||||
"text": "Third block of text"
|
"text": "Third block with "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "text",
|
||||||
|
"text": "bold",
|
||||||
|
"marks": [{ "type": "bold" }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "text",
|
||||||
|
"text": " text in it"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@@ -75,6 +75,22 @@ class Content extends React.Component {
|
|||||||
tagName: 'div',
|
tagName: 'div',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An error boundary. If there is a render error, we increment `errorKey`
|
||||||
|
* which is part of the container `key` which forces a re-render from
|
||||||
|
* scratch.
|
||||||
|
*
|
||||||
|
* @param {Error} error
|
||||||
|
* @param {String} info
|
||||||
|
*/
|
||||||
|
|
||||||
|
componentDidCatch(error, info) {
|
||||||
|
debug('componentDidCatch', { error, info })
|
||||||
|
// The call to `setState` is required despite not setting a value.
|
||||||
|
// Without this call, React will not try to recreate the component tree.
|
||||||
|
this.setState({})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Temporary values.
|
* Temporary values.
|
||||||
*
|
*
|
||||||
|
Reference in New Issue
Block a user