-
-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for componentDidCatch Component method #819
Changes from 5 commits
d54d337
6ff8cc6
721b437
dde11b2
e105500
3705d4e
e8776c7
980b4cb
89efb5e
b671425
31f5fd9
3a3d7cb
313ffe6
d4612f4
1f164dd
b35c534
aa48769
a2b4c8c
4320ff9
4338a7c
74fddde
65ff737
8b23777
308d8aa
b853143
30cde7b
b98159a
286e6ea
be4dee5
a019562
3a2a39f
e7cfc20
3ef85c4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -49,6 +49,18 @@ export function setComponentProps(component, props, opts, context, mountAll) { | |
if (component.__ref) component.__ref(component); | ||
} | ||
|
||
export function catchErrorInComponent(error, component) { | ||
for (; component; component = component._ancestorComponent) { | ||
if (component.componentDidCatch) { | ||
try { | ||
return component.componentDidCatch(error); | ||
} catch (e) { | ||
error = e; | ||
} | ||
} | ||
} | ||
throw error; | ||
} | ||
|
||
|
||
/** Render a Component, triggering necessary lifecycle events and taking High-Order Components into account. | ||
|
@@ -105,40 +117,45 @@ export function renderComponent(component, opts, mountAll, isChild) { | |
let childComponent = rendered && rendered.nodeName, | ||
toUnmount, base; | ||
|
||
if (typeof childComponent==='function') { | ||
// set up high order component link | ||
try { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the try/catch I was referring to in my other review comment. We can/should call |
||
if (typeof childComponent==='function') { | ||
// set up high order component link | ||
|
||
let childProps = getNodeProps(rendered); | ||
inst = initialChildComponent; | ||
let childProps = getNodeProps(rendered); | ||
inst = initialChildComponent; | ||
|
||
if (inst && inst.constructor===childComponent && childProps.key==inst.__key) { | ||
setComponentProps(inst, childProps, SYNC_RENDER, context, false); | ||
} | ||
else { | ||
toUnmount = inst; | ||
if (inst && inst.constructor===childComponent && childProps.key==inst.__key) { | ||
setComponentProps(inst, childProps, SYNC_RENDER, context, false); | ||
} | ||
else { | ||
toUnmount = inst; | ||
|
||
component._component = inst = createComponent(childComponent, childProps, context, component); | ||
inst.nextBase = inst.nextBase || nextBase; | ||
inst._parentComponent = component; | ||
setComponentProps(inst, childProps, NO_RENDER, context, false); | ||
renderComponent(inst, SYNC_RENDER, mountAll, true); | ||
} | ||
|
||
component._component = inst = createComponent(childComponent, childProps, context); | ||
inst.nextBase = inst.nextBase || nextBase; | ||
inst._parentComponent = component; | ||
setComponentProps(inst, childProps, NO_RENDER, context, false); | ||
renderComponent(inst, SYNC_RENDER, mountAll, true); | ||
base = inst.base; | ||
} | ||
else { | ||
cbase = initialBase; | ||
|
||
base = inst.base; | ||
} | ||
else { | ||
cbase = initialBase; | ||
|
||
// destroy high order component link | ||
toUnmount = initialChildComponent; | ||
if (toUnmount) { | ||
cbase = component._component = null; | ||
} | ||
// destroy high order component link | ||
toUnmount = initialChildComponent; | ||
if (toUnmount) { | ||
cbase = component._component = null; | ||
} | ||
|
||
if (initialBase || opts===SYNC_RENDER) { | ||
if (cbase) cbase._component = null; | ||
base = diff(cbase, rendered, context, mountAll || !isUpdate, initialBase && initialBase.parentNode, true); | ||
if (initialBase || opts===SYNC_RENDER) { | ||
if (cbase) cbase._component = null; | ||
base = diff(cbase, rendered, context, mountAll || !isUpdate, initialBase && initialBase.parentNode, component); | ||
} | ||
} | ||
} catch (e) { | ||
base = initialBase || document.createTextNode(""); | ||
catchErrorInComponent(e, component); | ||
} | ||
|
||
if (initialBase && base!==initialBase && inst!==initialChildComponent) { | ||
|
@@ -179,7 +196,11 @@ export function renderComponent(component, opts, mountAll, isChild) { | |
// flushMounts(); | ||
|
||
if (component.componentDidUpdate) { | ||
component.componentDidUpdate(previousProps, previousState, previousContext); | ||
try { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. try/catch is expensive, can we avoid doing this unless the component or an ancestor implements componentDidCatch? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe the check would be more expensive because you have to check every ancestor of the component, which means the complexity goes up as your tree gets deeper. Its really a question of O(n) vs O(1), and with modern browsers, try/catch isn't even that expensive. https://jsperf.com/try-catch-jsperf Created a JSPerf to check, and it seems like the checking for the existence of the method on just the component is more expensive then a standard try-catch There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 thanks for checking |
||
component.componentDidUpdate(previousProps, previousState, previousContext); | ||
} catch (e) { | ||
catchErrorInComponent(e, component._ancestorComponent); | ||
} | ||
} | ||
if (options.afterUpdate) options.afterUpdate(component); | ||
} | ||
|
@@ -199,7 +220,7 @@ export function renderComponent(component, opts, mountAll, isChild) { | |
* @returns {Element} dom The created/mutated element | ||
* @private | ||
*/ | ||
export function buildComponentFromVNode(dom, vnode, context, mountAll) { | ||
export function buildComponentFromVNode(dom, vnode, context, mountAll, ancestorComponent) { | ||
let c = dom && dom._component, | ||
originalComponent = c, | ||
oldDom = dom, | ||
|
@@ -220,7 +241,7 @@ export function buildComponentFromVNode(dom, vnode, context, mountAll) { | |
dom = oldDom = null; | ||
} | ||
|
||
c = createComponent(vnode.nodeName, props, context); | ||
c = createComponent(vnode.nodeName, props, context, ancestorComponent); | ||
if (dom && !c.nextBase) { | ||
c.nextBase = dom; | ||
// passing dom/oldDom as nextBase will recycle it if unused, so bypass recycling on L229: | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need another try/catch here? I would think that if
componentDidCatch
exists, the error is passed to it & it ends there. If the method does not exist, it's uncaught so the component (tree) is unmounted.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Errors that occur in
componentDidCatch
should bubble to ancestors, regardless of what lifecycle method generated the error or from what path that lifecycle method occurred. Error paths should be rare, so it's fine for it to be slow.