Skip to content

support errorInfo in onRecoverableError #24591

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

Merged
merged 9 commits into from
Jun 6, 2022

Conversation

gnoff
Copy link
Collaborator

@gnoff gnoff commented May 20, 2022

errorInfo has been used in Error Boundaries wiht componentDidCatch for a while now. To date this metadata only contained a componentStack. onRecoverableError only receives an error (type mixed) argument and thus providing additional error metadata was not possible without mutating user created mixed objects.

This change modifies rootConcurrentErrors rootRecoverableErrors, and hydrationErrors so all expect CapturedValue types. additionally a new factory function allows the creation of CapturedValues from a value plus a hash and stack.

In general, client derived CapturedValues will be created using the original function which derives a componentStack from a fiber and server originated CapturedValues will be created using with a passed in hash and optional componentStack.

This PR builds on #24551 and once that is merged I will rebase this PR to clean up the diff

@sizebot
Copy link

sizebot commented May 20, 2022

Comparing: 4ddd8b4...3b86e0f

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name +/- Base Current +/- gzip Base gzip Current gzip
oss-stable/react-dom/cjs/react-dom.production.min.js +0.10% 131.54 kB 131.67 kB +0.09% 42.26 kB 42.30 kB
oss-experimental/react-dom/cjs/react-dom.production.min.js +0.10% 136.80 kB 136.94 kB +0.10% 43.81 kB 43.85 kB
facebook-www/ReactDOM-prod.classic.js +0.09% 439.85 kB 440.25 kB +0.09% 80.44 kB 80.51 kB
facebook-www/ReactDOM-prod.modern.js +0.09% 425.14 kB 425.54 kB +0.10% 78.28 kB 78.37 kB
facebook-www/ReactDOMForked-prod.classic.js +0.09% 439.85 kB 440.25 kB +0.09% 80.45 kB 80.51 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name +/- Base Current +/- gzip Base gzip Current gzip
facebook-www/ReactDOMServer-prod.modern.js +0.43% 78.01 kB 78.34 kB +0.40% 16.35 kB 16.42 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.browser.production.min.js +0.38% 34.04 kB 34.17 kB +0.44% 11.32 kB 11.37 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.browser.production.min.js +0.38% 34.06 kB 34.19 kB +0.42% 11.34 kB 11.39 kB
oss-stable-semver/react-dom/umd/react-dom-server-legacy.browser.production.min.js +0.37% 34.15 kB 34.28 kB +0.32% 11.45 kB 11.49 kB
oss-stable/react-dom/umd/react-dom-server-legacy.browser.production.min.js +0.37% 34.18 kB 34.30 kB +0.31% 11.47 kB 11.51 kB
oss-experimental/react-dom/cjs/react-dom-server-legacy.browser.production.min.js +0.37% 34.48 kB 34.61 kB +0.43% 11.49 kB 11.54 kB
oss-experimental/react-dom/umd/react-dom-server-legacy.browser.production.min.js +0.37% 34.59 kB 34.72 kB +0.37% 11.61 kB 11.65 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.node.production.min.js +0.34% 37.76 kB 37.89 kB +0.35% 12.52 kB 12.56 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.node.production.min.js +0.34% 37.79 kB 37.91 kB +0.35% 12.54 kB 12.58 kB
oss-experimental/react-dom/cjs/react-dom-server-legacy.node.production.min.js +0.33% 38.26 kB 38.39 kB +0.34% 12.70 kB 12.74 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.browser.development.server.js +0.26% 62.81 kB 62.98 kB +0.14% 16.31 kB 16.34 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.browser.development.server.js +0.26% 62.81 kB 62.98 kB +0.14% 16.31 kB 16.34 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.browser.development.server.js +0.26% 62.87 kB 63.03 kB +0.13% 16.33 kB 16.36 kB
oss-stable-semver/react-server-dom-webpack/umd/react-server-dom-webpack-writer.browser.development.server.js +0.25% 66.03 kB 66.20 kB +0.13% 16.54 kB 16.56 kB
oss-stable/react-server-dom-webpack/umd/react-server-dom-webpack-writer.browser.development.server.js +0.25% 66.03 kB 66.20 kB +0.13% 16.54 kB 16.56 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.node.development.server.js +0.25% 64.51 kB 64.67 kB +0.13% 16.47 kB 16.49 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.node.development.server.js +0.25% 64.51 kB 64.67 kB +0.13% 16.47 kB 16.49 kB
oss-experimental/react-server-dom-webpack/umd/react-server-dom-webpack-writer.browser.development.server.js +0.25% 66.09 kB 66.26 kB +0.15% 16.56 kB 16.58 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.node.development.server.js +0.25% 64.56 kB 64.72 kB +0.14% 16.49 kB 16.51 kB
facebook-react-native/react-test-renderer/cjs/ReactTestRenderer-prod.js +0.21% 257.64 kB 258.18 kB +0.26% 47.00 kB 47.12 kB
oss-stable-semver/react-art/cjs/react-art.production.min.js +0.21% 81.69 kB 81.86 kB +0.16% 25.47 kB 25.51 kB
oss-stable/react-art/cjs/react-art.production.min.js +0.21% 81.72 kB 81.89 kB +0.16% 25.47 kB 25.51 kB
facebook-react-native/react-test-renderer/cjs/ReactTestRenderer-profiling.js +0.20% 272.71 kB 273.25 kB +0.26% 49.24 kB 49.36 kB

Generated by 🚫 dangerJS against 3b86e0f

Copy link
Collaborator

@sebmarkbage sebmarkbage left a comment

Choose a reason for hiding this comment

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

I think it looks good but would be good to double check with @acdlite.

Let's bikeshed the name of the errorHash a bit too.

onRecoverableError: (error: mixed) => void,
onRecoverableError: (
error: mixed,
errorInfo: {errorHash?: ?string, componentStack?: ?string},
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is the first place we actually name errorHash anything in a public API. It's intended to be a hash but you could use it in other ways too (including just returning the full error message in production). I wonder if we should call it something more generic?

Copy link
Collaborator

@sebmarkbage sebmarkbage Jun 1, 2022

Choose a reason for hiding this comment

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

errorCode or maybe errorId?

It could be a generated ID too. Doesn't need to be a hash.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I like errorCode, feels less prescriptive than hash or id. similarly errorDigest evokes some potentially summarized error data and has analogs with crypto hashing as the output so it works kinda however you use it.

Code perhaps implies codification too much

Copy link
Collaborator

Choose a reason for hiding this comment

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

One downside of errorCode is that it's often associated with the minified error codes.

Maybe that should actually be a special field too for minified errors so you don't have to parse it yourself. Even for client errors.

(If you don't provide onError on the server and it's a React error, maybe we should use the minified error code as the default "hash" too.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

So

{
  // For React errors in Prod Server or Client: the React Error Code
  code?: string,
  
  // For Server user errors: onError return value
  // For Server React errors if no onError provided: the React Error Code
  digest? string, // or whatever this name becomes
  
  // For Dev Server errors (React or user): the server component stack
  // For Dev or Prod Client errors (React or user): the client component stack
  componentStack?: string,
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

Sounds good. Let's do the code part later though.

@sebmarkbage sebmarkbage requested a review from acdlite May 27, 2022 18:56
const nextSibling = instance.nextSibling;
let errorMessage /*, errorComponentStack, errorHash*/;
if (
nextSibling &&
nextSibling.nodeType === ELEMENT_NODE &&
nextSibling.nodeName.toLowerCase() === 'template'
) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

You can probably just assume this to be the case. If not, you most likely just get an empty data set.

// if (stack !== null) errorComponentStack = stack;
return {
message: ((nextSibling: any): HTMLTemplateElement).dataset.msg,
stack: ((nextSibling: any): HTMLTemplateElement).dataset.stack,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should this be DEV only?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

What's your take on mode divergence on server/client? Should a prod client hitting a dev server not bubble up stacks if they are present?

Copy link
Collaborator

Choose a reason for hiding this comment

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

They should really just always be on the same mode and ideally warn if they're not.

@@ -2662,22 +2675,23 @@ function updateDehydratedSuspenseComponent(
// This boundary is in a permanent fallback state. In this case, we'll never
// get an update and we'll never be able to hydrate the final content. Let's just try the
// client side render instead.
const {errorMessage} = getSuspenseInstanceFallbackErrorDetails(
const {message, hash, stack} = getSuspenseInstanceFallbackErrorDetails(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Does this intermediate object get DCE?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

the object ErrorDetails returns is not DCE'd but the destructuring does not create an additional object the properties just get assigned directly to local variables.

I considered 3 separate functions to return each part but that seems excessive for an error path. I also considered having ErrorDetails directly return a CapturedValue but that is pushing a new concept into ReactDOMHostConfig and I was unsure if that was wise.

How concerned are you with the object creation in ErrorDetails?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think you got the tradeoff right. It's just unfortunate that Closure isn't smart enough. Seems like it should be able to.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe if you always returned the same object with the same signature at the end and just assigned to local variables before it?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah I got this to work

@gnoff gnoff force-pushed the on-recoverable-error branch 2 times, most recently from c17a9ce to 553420e Compare June 2, 2022 05:47

// const stack = ((nextSibling: any): HTMLTemplateElement).dataset.stack;
// if (stack !== null) errorComponentStack = stack;
): {digest: ?string, message?: string, stack?: string} {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@sebmarkbage best way to type functions differently between DEV and prod? I could make it two functions I suppose

gnoff added 5 commits June 6, 2022 09:09
errorInfo has been used in Error Boundaries wiht componentDidCatch for a while now. To date this metadata only contained a componentStack. onRecoverableError only receives an error (type mixed) argument and thus providing additional error metadata was not possible without mutating user created mixed objects.

This change modifies rootConcurrentErrors rootRecoverableErrors, and hydrationErrors so all expect CapturedValue types. additionally a new factory function allows the creation of CapturedValues from a value plus a hash and stack.

In general, client derived CapturedValues will be created using the original function which derives a componentStack from a fiber and server originated CapturedValues will be created using with a passed in hash and optional componentStack.
@gnoff gnoff force-pushed the on-recoverable-error branch from 9214ca9 to 8b596cd Compare June 6, 2022 16:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants