Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Move editor destroying to TinyMCE component
  • Loading branch information
ellatrix committed May 4, 2017
commit 69e5bb73e3d07a714d20ff6cc011436163e58d56
11 changes: 1 addition & 10 deletions blocks/components/editable/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -252,17 +252,8 @@ export default class Editable extends wp.element.Component {
}
}

componentWillUpdate( nextProps ) {
if ( this.editor && this.props.tagName !== nextProps.tagName ) {
this.editor.destroy();
}
}

componentWillUnmount() {
if ( this.editor ) {
this.onChange();
this.editor.destroy();
}
this.onChange();
}

componentDidUpdate( prevProps ) {
Expand Down
61 changes: 42 additions & 19 deletions blocks/components/editable/tinymce.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,42 @@
export default class TinyMCE extends wp.element.Component {
componentDidMount() {
this.initialize();
}

shouldComponentUpdate( nextProps ) {
// We must prevent rerenders because TinyMCE will modify the DOM, thus
// breaking React's ability to reconcile changes.
//
// See: https://github.com/facebook/react/issues/6802
return nextProps.tagName !== this.props.tagName;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm surprised to find that this doesn't really break. I think it'd be worth updating the preceding comment to describe when and why we allow a rerender, and ideally how it's exempt from the concern noted in the referenced issue.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this sound okay?

// If the tag changes, React can reconcile the node in place after the
// editor is destroyed.

}

componentWillUpdate( nextProps ) {
if ( ! this.editor ) {
return;
}

if ( nextProps.tagName !== this.props.tagName ) {
this.editor.destroy();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this how React is able to reconcile? That we're unmounting the node altogether and React recreates it in the next pass?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The node and contents will still be there. Is that was you mean?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The node and contents will still be there. Is that was you mean?

It's what I was curious about, yes, but opposite of the assumption I was making. In #635 I'd encountered issues where React would reconcile based on the DOM that TinyMCE had since modified, resulting in an infinite loop of errors, particularly around splitting and merging blocks. With these changes I'm not seeing this issue, but I don't feel confident in knowing why it's not a problem.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I'm not sure why it was a problem.

this.editor = null;
}
}

componentDidUpdate( prevProps ) {
if ( this.props.tagName !== prevProps.tagName ) {
this.initialize();
}
}

componentWillUnmount() {
if ( ! this.editor ) {
Copy link
Member

@aduth aduth May 4, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dunno if it's worth the abstraction, but I could see value in a destroy method which handles (a) verifying editor instance property, (b) destroying editor instance, (c) unsetting instance property:

destroy() {
	if ( this.editor ) {
		this.editor.destroy();
		delete this.editor;
	}
}

To the point of delete vs = null;, I don't feel strongly one way or the other, but I think in cases where we assign any instance property, it would be a good idea to initialize it to its default value in constructor so that it's clearer which instance properties exist in a class.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah you're right, this is cleaner.

return;
}

this.editor.destroy();
this.editor = null;
}

initialize() {
Copy link
Member

@aduth aduth May 4, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally, we might want to consider conventions for component method ordering. I usually like to see:

  • Static properties
  • Constructor
  • Lifecycle methods
  • Miscellaneous
  • Render

(Considering this as "miscellaneous")

tinymce.init( {
target: this.editorNode,
Expand All @@ -8,7 +46,10 @@ export default class TinyMCE extends wp.element.Component {
browser_spellcheck: true,
entity_encoding: 'raw',
convert_urls: false,
setup: this.props.onSetup,
setup: ( editor ) => {
this.editor = editor;
this.props.onSetup( editor );
},
formats: {
strikethrough: { inline: 'del' }
}
Expand All @@ -19,24 +60,6 @@ export default class TinyMCE extends wp.element.Component {
}
}

componentDidMount() {
this.initialize();
}

componentDidUpdate( prevProps ) {
if ( this.props.tagName !== prevProps.tagName ) {
this.initialize();
}
}

shouldComponentUpdate( nextProps ) {
// We must prevent rerenders because TinyMCE will modify the DOM, thus
// breaking React's ability to reconcile changes.
//
// See: https://github.com/facebook/react/issues/6802
return nextProps.tagName !== this.props.tagName;
}

render() {
const { tagName = 'div', style, className, defaultValue } = this.props;

Expand Down