From 977ce2e42fdcc04a610fdabfae6e58b27c45e4a3 Mon Sep 17 00:00:00 2001 From: Tomas Milar Date: Tue, 2 Feb 2021 19:25:27 -0300 Subject: [PATCH 1/4] fix: properly handle render errors in 'react' driver. --- src/drivers/react.js | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/drivers/react.js b/src/drivers/react.js index 774cbf6c..7e0941c9 100644 --- a/src/drivers/react.js +++ b/src/drivers/react.js @@ -31,6 +31,21 @@ type ReactLibraryType = {| ReactDOM : ReactDomType |}; + +/** + * Util to check if component is currently mounted + */ +function isMounted(component, ReactDOM) { + try { + return !!ReactDOM.findDOMNode(component); + } + catch (error) { + // Error: Unable to find node on an unmounted component + return false; + } +} + + export const react : ComponentDriverType<*, ReactLibraryType, typeof ReactClassType> = { register: (tag, propsDef, init, { React, ReactDOM }) => { @@ -45,7 +60,18 @@ export const react : ComponentDriverType<*, ReactLibraryType, typeof ReactClassT // $FlowFixMe const el = ReactDOM.findDOMNode(this); const parent = init(extend({}, this.props)); - parent.render(el, CONTEXT.IFRAME); + parent.render(el, CONTEXT.IFRAME) + .catch(error => { + // component failed to render, possibly because it was closed or destroyed. + if(!isMounted(this, ReactDOM)) { + // not mounted anymore, we can safely ignore the error + return + } + // still mounted, throw error inside react to allow a parent component or ErrorBoundary to handle it + this.setState(() => { + throw error + }) + }); this.setState({ parent }); } From e5780f493ed81bed8c35e26c87a93f12a428e50f Mon Sep 17 00:00:00 2001 From: Tomas Milar Date: Thu, 4 Feb 2021 10:25:42 -0300 Subject: [PATCH 2/4] fix(react): lint errors. --- src/drivers/react.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/drivers/react.js b/src/drivers/react.js index 7e0941c9..84f7f26b 100644 --- a/src/drivers/react.js +++ b/src/drivers/react.js @@ -35,9 +35,9 @@ type ReactLibraryType = {| /** * Util to check if component is currently mounted */ -function isMounted(component, ReactDOM) { +function isMounted(component, ReactDOM) : boolean { try { - return !!ReactDOM.findDOMNode(component); + return Boolean(ReactDOM.findDOMNode(component)); } catch (error) { // Error: Unable to find node on an unmounted component @@ -63,14 +63,14 @@ export const react : ComponentDriverType<*, ReactLibraryType, typeof ReactClassT parent.render(el, CONTEXT.IFRAME) .catch(error => { // component failed to render, possibly because it was closed or destroyed. - if(!isMounted(this, ReactDOM)) { + if (!isMounted(this, ReactDOM)) { // not mounted anymore, we can safely ignore the error - return + return; } // still mounted, throw error inside react to allow a parent component or ErrorBoundary to handle it this.setState(() => { - throw error - }) + throw error; + }); }); this.setState({ parent }); } From 3d698b84190d85324b9739b26b173a09a050c262 Mon Sep 17 00:00:00 2001 From: Tomas Milar Date: Thu, 4 Feb 2021 10:56:53 -0300 Subject: [PATCH 3/4] fix(react): add isMounted() typings to satisfy flow checks. --- src/drivers/react.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/drivers/react.js b/src/drivers/react.js index 84f7f26b..7cd40121 100644 --- a/src/drivers/react.js +++ b/src/drivers/react.js @@ -35,7 +35,7 @@ type ReactLibraryType = {| /** * Util to check if component is currently mounted */ -function isMounted(component, ReactDOM) : boolean { +function isMounted(component : typeof ReactClassType, ReactDOM : ReactDomType) : boolean { try { return Boolean(ReactDOM.findDOMNode(component)); } From 7e05c6c601412582237ff4641ed5e43146750018 Mon Sep 17 00:00:00 2001 From: Tomas Milar Date: Thu, 4 Feb 2021 13:10:02 -0300 Subject: [PATCH 4/4] fix(react): fix all 'react' driver flow / lint errors. --- src/drivers/react.js | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/drivers/react.js b/src/drivers/react.js index 7cd40121..e85e74c3 100644 --- a/src/drivers/react.js +++ b/src/drivers/react.js @@ -3,27 +3,34 @@ import { extend, noop } from 'belter/src'; -import type { ComponentDriverType } from '../component'; +import type { ComponentDriverType, ZoidComponentInstance } from '../component'; import { CONTEXT } from '../constants'; // eslint-disable-next-line flowtype/require-exact-type declare class ReactClassType {} // eslint-disable-next-line flowtype/require-exact-type -declare class __ReactComponent {} +declare class _ReactComponentType { + state : {| parent? : ZoidComponentInstance<*> |}, + props : mixed, + setState : (newStateOrFn : mixed) => void +} + +// eslint-disable-next-line flowtype/require-exact-type +type Class = { new(): T }; type ReactElementType = {| |}; type ReactType = {| - Component : __ReactComponent, + Component : typeof _ReactComponentType, createClass : ({| render : () => ReactElementType, componentDidMount : () => void, componentDidUpdate : () => void |}) => (typeof ReactClassType), createElement : (string, ?{ [string] : mixed }, ...children : $ReadOnlyArray) => ReactElementType |}; type ReactDomType = {| - findDOMNode : (typeof ReactClassType) => HTMLElement + findDOMNode : (_ReactComponentType) => HTMLElement |}; type ReactLibraryType = {| @@ -35,7 +42,7 @@ type ReactLibraryType = {| /** * Util to check if component is currently mounted */ -function isMounted(component : typeof ReactClassType, ReactDOM : ReactDomType) : boolean { +function isMounted(component : _ReactComponentType, ReactDOM : ReactDomType) : boolean { try { return Boolean(ReactDOM.findDOMNode(component)); } @@ -45,19 +52,16 @@ function isMounted(component : typeof ReactClassType, ReactDOM : ReactDomType) : } } +export const react : ComponentDriverType<*, ReactLibraryType, Class<_ReactComponentType>> = { -export const react : ComponentDriverType<*, ReactLibraryType, typeof ReactClassType> = { - - register: (tag, propsDef, init, { React, ReactDOM }) => { + register: (tag, propsDef, init, { React, ReactDOM }) : Class<_ReactComponentType> => { - // $FlowFixMe - return class extends React.Component { + return class ZoidReactComponent extends React.Component { render() : ReactElementType { return React.createElement('div', null); } componentDidMount() { - // $FlowFixMe const el = ReactDOM.findDOMNode(this); const parent = init(extend({}, this.props)); parent.render(el, CONTEXT.IFRAME)