-
Notifications
You must be signed in to change notification settings - Fork 47k
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 onRecoverableError option to hydrateRoot, createRoot #23207
Changes from 4 commits
cb1c764
0f23548
667e431
1ee9f60
2ab18ff
73ff091
61ef8f4
24abe60
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 | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -70,6 +70,7 @@ import {HostComponent, HostText} from 'react-reconciler/src/ReactWorkTags'; | |||||||||
import {listenToAllSupportedEvents} from '../events/DOMPluginEventSystem'; | ||||||||||
|
||||||||||
import {DefaultEventPriority} from 'react-reconciler/src/ReactEventPriorities'; | ||||||||||
import {scheduleCallback, IdlePriority} from 'react-reconciler/src/Scheduler'; | ||||||||||
|
||||||||||
export type Type = string; | ||||||||||
export type Props = { | ||||||||||
|
@@ -123,6 +124,10 @@ export type TimeoutHandle = TimeoutID; | |||||||||
export type NoTimeout = -1; | ||||||||||
export type RendererInspectionConfig = $ReadOnly<{||}>; | ||||||||||
|
||||||||||
// Right now this is a single callback, but could be multiple in the in the | ||||||||||
// future. | ||||||||||
export type ErrorLoggingConfig = null | ((error: mixed) => void); | ||||||||||
|
||||||||||
type SelectionInformation = {| | ||||||||||
focusedElem: null | HTMLElement, | ||||||||||
selectionRange: mixed, | ||||||||||
|
@@ -374,6 +379,25 @@ export function getCurrentEventPriority(): * { | |||||||||
return getEventPriority(currentEvent.type); | ||||||||||
} | ||||||||||
|
||||||||||
export function logRecoverableError( | ||||||||||
config: ErrorLoggingConfig, | ||||||||||
error: mixed, | ||||||||||
): void { | ||||||||||
const onRecoverableError = config; | ||||||||||
if (onRecoverableError !== null) { | ||||||||||
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 feels like a reconciler specific concern to me. Especially since this is likely to grow with other types of callbacks that will have more advanced semantics than this callback will handle. One case I'd expect we might want is to scope this to subtree. Like error boundaries but for recoverable errors. The reconciler can make this choice and schedule an IdlePriority callback itself if it wants to. Although it feels a bit presumptuous to do that for the user since you can schedule it in the callback. Although paternalism is on brand for us I guess. 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. Yeah I went back and forth on that. The main reason I ended up adding a host config is because it needs a default fallback behavior; didn't feel right to assume that throwing in a Scheduler task is necessarily the correct default for each environment (although I suppose that's what we do for uncaught errors). An alternative is to resolve the default option within the renderer before passing it to the root constructor but that feels like a superficial difference. Don't mind either way. Related comment: react/packages/react-reconciler/src/ReactFiberRoot.new.js Lines 114 to 117 in e6a1df0
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. Yea I don't think it's always correct to throw in a task so having host config makes sense for the default fallback behavior. It's the main API behavior that doesn't seem right to determine in the host config. In other words, resolving the default option within the renderer would make sense. That also avoids leaking the type to all the host configs. |
||||||||||
// Schedule a callback to invoke the user-provided logging function. | ||||||||||
scheduleCallback(IdlePriority, () => { | ||||||||||
onRecoverableError(error); | ||||||||||
}); | ||||||||||
} else { | ||||||||||
// Default behavior is to rethrow the error in a separate task. This will | ||||||||||
// trigger a browser error event. | ||||||||||
scheduleCallback(IdlePriority, () => { | ||||||||||
throw error; | ||||||||||
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. There's a handleErrorInNextTick function in this file that has different semantics but I guess is similarly a fallback default behavior? Should they be unified? 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. Looks like that method is only used by the |
||||||||||
}); | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
export const isPrimaryRenderer = true; | ||||||||||
export const warnsIfNotActing = true; | ||||||||||
// This initialization code may run even on server environments | ||||||||||
|
@@ -1070,6 +1094,8 @@ export function didNotFindHydratableSuspenseInstance( | |||||||||
|
||||||||||
export function errorHydratingContainer(parentContainer: Container): void { | ||||||||||
if (__DEV__) { | ||||||||||
// TODO: This gets logged by onRecoverableError, too, so we should be | ||||||||||
// able to remove it. | ||||||||||
console.error( | ||||||||||
'An error occurred during hydration. The server HTML was replaced with client content in <%s>.', | ||||||||||
parentContainer.nodeName.toLowerCase(), | ||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -214,6 +214,7 @@ function render( | |
false, | ||
null, | ||
'', | ||
null, | ||
); | ||
roots.set(containerTag, root); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -95,6 +95,8 @@ export type RendererInspectionConfig = $ReadOnly<{| | |
) => void, | ||
|}>; | ||
|
||
export type ErrorLoggingConfig = null; | ||
|
||
// TODO: Remove this conditional once all changes have propagated. | ||
if (registerEventHandler) { | ||
/** | ||
|
@@ -525,3 +527,10 @@ export function preparePortalMount(portalInstance: Instance): void { | |
export function detachDeletedInstance(node: Instance): void { | ||
// noop | ||
} | ||
|
||
export function logRecoverableError( | ||
config: ErrorLoggingConfig, | ||
error: mixed, | ||
): void { | ||
// noop | ||
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. console.error or something maybe to start with? |
||
} |
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.
For my own memory later, the reason this wasn't already something that existed for throwing top level is because other top level errors are handled by throwing at the root of the work loop and letting whatever is above handle it.