From bbf7e8ed7ababd351b7051ca2e3f8c78335b5e65 Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Thu, 28 Jul 2022 15:52:50 +0100 Subject: [PATCH] Cleaned up react component rendering no issue - switched from needing to extend from `ReactComponent` to using a `{{react-render}}` modifier - modifiers are modern idiomatic Ember for handing "did-insert" hooks and associated lifecycle - moved code from `` into `` - no need for the extra layering of components and need to remember two places to modify when adding passthrough args/props --- .../app/components/koenig-react-editor.hbs | 1 + .../app/components/koenig-react-editor.js | 100 +++++++++++++++--- ghost/admin/app/components/react-component.js | 46 -------- .../app/components/react-mobiledoc-editor.js | 88 --------------- ghost/admin/app/modifiers/react-render.js | 23 ++++ 5 files changed, 110 insertions(+), 148 deletions(-) create mode 100644 ghost/admin/app/components/koenig-react-editor.hbs delete mode 100644 ghost/admin/app/components/react-component.js delete mode 100644 ghost/admin/app/components/react-mobiledoc-editor.js create mode 100644 ghost/admin/app/modifiers/react-render.js diff --git a/ghost/admin/app/components/koenig-react-editor.hbs b/ghost/admin/app/components/koenig-react-editor.hbs new file mode 100644 index 0000000000..ed901509b3 --- /dev/null +++ b/ghost/admin/app/components/koenig-react-editor.hbs @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/ghost/admin/app/components/koenig-react-editor.js b/ghost/admin/app/components/koenig-react-editor.js index 4e68749735..0fea1ed9ca 100644 --- a/ghost/admin/app/components/koenig-react-editor.js +++ b/ghost/admin/app/components/koenig-react-editor.js @@ -1,17 +1,89 @@ -import ReactComponent from './react-component'; -import ReactMobiledocEditor from './react-mobiledoc-editor'; -import {action} from '@ember/object'; +import Component from '@glimmer/component'; +import React, {Suspense} from 'react'; -export default class KoenigReactEditor extends ReactComponent { - @action - renderComponent(element) { - this.reactRender( - element, - - ); +class ErrorHandler extends React.Component { + state = { + hasError: false + }; + + static getDerivedStateFromError() { + return {hasError: true}; + } + + render() { + if (this.state.hasError) { + return ( +

Loading has failed. Try refreshing the browser!

+ ); + } + + return this.props.children; } } + +const fetchKoenig = function () { + let status = 'pending'; + let response; + + const fetchPackage = async () => { + if (window.koenigEditor) { + return window.koenigEditor.default; + } + + // the removal of `https://` and it's manual addition to the import template string is + // required to work around ember-auto-import complaining about an unknown dynamic import + // during the build step + const GhostAdmin = window.Ember.Namespace.NAMESPACES.find(ns => ns.name === 'ghost-admin'); + const url = GhostAdmin.__container__.lookup('service:config').get('editor.url').replace('https://', ''); + await import(`https://${url}`); + + return window.koenigEditor.default; + }; + + const suspender = fetchPackage().then( + (res) => { + status = 'success'; + response = res; + }, + (err) => { + status = 'error'; + response = err; + } + ); + + const read = () => { + switch (status) { + case 'pending': + throw suspender; + case 'error': + throw response; + default: + return response; + } + }; + + return {read}; +}; + +const editorResource = fetchKoenig(); + +const Koenig = (props) => { + const _Koenig = editorResource.read(); + return <_Koenig {...props} />; +}; + +export default class KoenigReactEditor extends Component { + ReactComponent = () => { + return ( + + Loading editor...

}> + +
+
+ ); + }; +} diff --git a/ghost/admin/app/components/react-component.js b/ghost/admin/app/components/react-component.js deleted file mode 100644 index 2d24b8d0af..0000000000 --- a/ghost/admin/app/components/react-component.js +++ /dev/null @@ -1,46 +0,0 @@ -/* global ReactDOM */ -import Component from '@glimmer/component'; -import {action} from '@ember/object'; - -export default class ReactComponent extends Component { - @action - renderComponent() { - // eslint-disable-next-line - console.error('Components extending ReactComponent must implement a `renderComponent()` action that calls `this.reactRender()'); - } - - /** - * Renders a react component as the current ember element - * @param {React.Component} reactComponent. e.g., - */ - reactRender(element, reactComponent) { - if (element !== this.elem) { - this.unmountReactElement(); - } - - this.elem = element; - this.root = ReactDOM.createRoot(this.elem); - this.root.render(reactComponent); - } - - /** - * Removes a mounted React component from the DOM and - * cleans up its event handlers and state. - */ - unmountReactElement() { - if (!this.root) { - return; - } - - this.root.unmount(); - } - - /** - * Cleans up the rendered react component as the ember - * component gets destroyed - */ - willDestroy() { - super.willDestroy(); - this.unmountReactElement(); - } -} diff --git a/ghost/admin/app/components/react-mobiledoc-editor.js b/ghost/admin/app/components/react-mobiledoc-editor.js deleted file mode 100644 index 4bd07cbb81..0000000000 --- a/ghost/admin/app/components/react-mobiledoc-editor.js +++ /dev/null @@ -1,88 +0,0 @@ -import React, {Suspense} from 'react'; - -class ErrorHandler extends React.Component { - state = { - hasError: false - }; - - static getDerivedStateFromError() { - return {hasError: true}; - } - - render() { - if (this.state.hasError) { - return ( -

Loading has failed. Try refreshing the browser!

- ); - } - - return this.props.children; - } -} - -const fetchKoenig = function () { - let status = 'pending'; - let response; - - const fetchPackage = async () => { - if (window.koenigEditor) { - return window.koenigEditor.default; - } - - // the removal of `https://` and it's manual addition to the import template string is - // required to work around ember-auto-import complaining about an unknown dynamic import - // during the build step - const GhostAdmin = window.Ember.Namespace.NAMESPACES.find(ns => ns.name === 'ghost-admin'); - const url = GhostAdmin.__container__.lookup('service:config').get('editor.url').replace('https://', ''); - await import(`https://${url}`); - - return window.koenigEditor.default; - }; - - const suspender = fetchPackage().then( - (res) => { - status = 'success'; - response = res; - }, - (err) => { - status = 'error'; - response = err; - } - ); - - const read = () => { - switch (status) { - case 'pending': - throw suspender; - case 'error': - throw response; - default: - return response; - } - }; - - return {read}; -}; - -const editorResource = fetchKoenig(); - -const Koenig = (props) => { - const KoenigEditor = editorResource.read(); - return ; -}; - -const ReactMobiledocEditor = (props) => { - return ( - - Loading editor...

}> - -
-
- ); -}; - -export default ReactMobiledocEditor; diff --git a/ghost/admin/app/modifiers/react-render.js b/ghost/admin/app/modifiers/react-render.js new file mode 100644 index 0000000000..46942a442e --- /dev/null +++ b/ghost/admin/app/modifiers/react-render.js @@ -0,0 +1,23 @@ +/* global ReactDOM */ +import Modifier from 'ember-modifier'; +import {createElement} from 'react'; + +export default class ReactRenderModifier extends Modifier { + didInstall() { + const [reactComponent] = this.args.positional; + const props = this.args.named; + + if (!this.root) { + this.root = ReactDOM.createRoot(this.element); + } + this.root.render(createElement(reactComponent, {...props})); + } + + willDestroy() { + if (!this.root) { + return; + } + + this.root.unmount(); + } +}