From ed77bf0fa16e57a16e31f26506e231d06f3647ac Mon Sep 17 00:00:00 2001 From: Ian Storm Taylor Date: Fri, 29 Sep 2017 11:12:13 -0700 Subject: [PATCH] fix change queueing in (#1196) --- packages/slate-react/src/components/editor.js | 72 ++++++++++++++++--- 1 file changed, 63 insertions(+), 9 deletions(-) diff --git a/packages/slate-react/src/components/editor.js b/packages/slate-react/src/components/editor.js index 37d7976ef..1ae9ad959 100644 --- a/packages/slate-react/src/components/editor.js +++ b/packages/slate-react/src/components/editor.js @@ -109,8 +109,8 @@ class Editor extends React.Component { constructor(props) { super(props) - this.tmp = {} this.state = {} + this.tmp = {} // Create a new `Stack`, omitting the `onChange` property since that has // special significance on the editor itself. @@ -118,10 +118,12 @@ class Editor extends React.Component { const stack = Stack.create({ plugins }) this.state.stack = stack - // Resolve the state, running `onBeforeChange` first. + // Run `onBeforeChange` on the passed-in state because we need to ensure + // that it is normalized, and queue the resulting change. const change = props.state.change() stack.onBeforeChange(change, this) const { state } = change + this.queueChange(change) this.cacheState(state) this.state.state = state @@ -164,17 +166,34 @@ class Editor extends React.Component { this.setState({ stack }) } - // Resolve the state, running the `onBeforeChange` handler of the stack. + // Run `onBeforeChange` on the passed-in state because we need to ensure + // that it is normalized, and queue the resulting change. const change = props.state.change() stack.onBeforeChange(change, this) const { state } = change + this.queueChange(change) this.cacheState(state) this.setState({ state }) } /** - * Cache a `state` in memory to be able to compare against it later, for - * things like `onDocumentChange`. + * When the component first mounts, flush any temporary changes. + */ + + componentDidMount = () => { + this.flushChange() + } + + /** + * When the component updates, flush any temporary change. + */ + + componentDidUpdate = () => { + this.flushChange() + } + + /** + * Cache a `state` object to be able to compare against it later. * * @param {State} state */ @@ -184,6 +203,37 @@ class Editor extends React.Component { this.tmp.selection = state.selection } + /** + * Queue a `change` object, to be able to flush it later. This is required for + * when a change needs to be applied to the state, but because of the React + * lifecycle we can't apply that change immediately. So we cache it here and + * later can call `this.flushChange()` to flush it. + * + * @param {Change} change + */ + + queueChange = (change) => { + if (change.operations.length) { + debug('queueChange', { change }) + this.tmp.change = change + } + } + + /** + * Flush a temporarily stored `change` object, for when a change needed to be + * made but couldn't because of React's lifecycle. + */ + + flushChange = () => { + const { change } = this.tmp + + if (change) { + debug('flushChange', { change }) + this.props.onChange(change) + delete this.tmp.change + } + } + /** * Programmatically blur the editor. */ @@ -229,6 +279,7 @@ class Editor extends React.Component { change = (fn) => { const change = this.state.state.change() fn(change) + debug('change', { change }) this.onChange(change) } @@ -243,15 +294,18 @@ class Editor extends React.Component { throw new Error('As of slate@0.22.0 the `editor.onChange` method must be passed a `Change` object not a `State` object.') } - const { onChange, onDocumentChange, onSelectionChange } = this.props const { stack } = this.state - const { document, selection } = this.tmp - const { state } = change - if (state == this.state.state) return stack.onBeforeChange(change, this) stack.onChange(change, this) + const { state } = change + const { document, selection } = this.tmp + const { onChange, onDocumentChange, onSelectionChange } = this.props + + if (state == this.state.state) return + + debug('onChange', { change }) onChange(change) if (onDocumentChange && state.document != document) onDocumentChange(state.document, change) if (onSelectionChange && state.selection != selection) onSelectionChange(state.selection, change)