Skip to content
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

React.warn() and React.error() #15170

Merged
merged 6 commits into from
Mar 21, 2019

Conversation

bvaughn
Copy link
Contributor

@bvaughn bvaughn commented Mar 20, 2019

Library authors often request a way to log warnings that include the React "component stack". This PR adds two new top-level APIs to for this purpose: React.warn and React.error

These methods avoid exposing the stack directly so that people aren't tempted to parse it. (They can obviously still do this by intercepting calls to console.error but at least the API doesn't encourage it.) Instead they wrap console.error and console.warn and append the component stack when available.

Usage

Example usage (borrowed from react-window):

import { warn } from "react";

function List(props) {
  if (process.env.NODE_ENV !== "production") {
    if (props.innerTagName != null || props.outerTagName != null) {
      warn(
        "The innerTagName and outerTagName props have been deprecated. " +
          "Please use the innerElementType and outerElementType props instead."
      );
    }
  }

  // ...
}

Which might log something like:

The innerTagName and outerTagName props have been deprecated. Please use the innerElementType and outerElementType props instead. 
    in List (at App.js:14)
    in div (at App.js:13)
    in App (at src/index.js:7)

Behavior

The behavior of these new methods is as follows:

Build phase calls console method? appends component stack?
development render, commit
development other
production any

@sizebot
Copy link

sizebot commented Mar 20, 2019

React: size: 🔺+0.3%, gzip: 🔺+0.2%

Details of bundled changes.

Comparing: 56035da...625aaf6

react

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react.development.js +1.0% +0.6% 100.42 KB 101.38 KB 26.15 KB 26.3 KB UMD_DEV
react.production.min.js 🔺+0.3% 🔺+0.2% 12.01 KB 12.04 KB 4.61 KB 4.62 KB UMD_PROD
react.profiling.min.js +0.2% +0.3% 14.16 KB 14.19 KB 5.12 KB 5.14 KB UMD_PROFILING
react.development.js +1.5% +1.0% 62.96 KB 63.92 KB 17.04 KB 17.2 KB NODE_DEV
react.production.min.js 🔺+0.5% 🔺+0.5% 6.37 KB 6.4 KB 2.64 KB 2.65 KB NODE_PROD
React-dev.js +1.6% +1.0% 61.35 KB 62.31 KB 16.36 KB 16.52 KB FB_WWW_DEV
React-prod.js 🔺+0.3% 🔺+0.5% 15.25 KB 15.3 KB 4.07 KB 4.09 KB FB_WWW_PROD
React-profiling.js +0.3% +0.5% 15.25 KB 15.3 KB 4.07 KB 4.09 KB FB_WWW_PROFILING

Generated by 🚫 dangerJS

Copy link
Contributor

@threepointone threepointone left a comment

Choose a reason for hiding this comment

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

This is going to be so good for open source land. LGTM.

@bvaughn
Copy link
Contributor Author

bvaughn commented Mar 21, 2019

Follow up:

  • React.warn and React.error (for console.warn and console.error)
  • Pass-throughs in DEV outside of render/commit phases.
  • No-ops in production.

@bvaughn bvaughn force-pushed the warn-with-component-stack branch from 257541f to 1078cc9 Compare March 21, 2019 16:54
@bvaughn bvaughn changed the title warnWithComponentStack() API React.warn() and React.error() Mar 21, 2019
@bvaughn bvaughn requested a review from threepointone March 21, 2019 17:19
@bvaughn
Copy link
Contributor Author

bvaughn commented Mar 21, 2019

Okay, PR has been updated~

@sophiebits
Copy link
Collaborator

Why make it a no-op in production, vs logging the message sans stack?

@bvaughn
Copy link
Contributor Author

bvaughn commented Mar 21, 2019

Why make it a no-op in production, vs logging the message sans stack?

To gently encourage people to use this API for DEV-only messaging.

@eps1lon
Copy link
Collaborator

eps1lon commented Mar 21, 2019

This makes library life so much easier 🙏

Could we change the signature to be equal to existing APIs such as warning or invariant?

In it's current form React.warn requires two levels of nesting to not appear in production. Would be nice if we could just™

- warning(someInvariant, message)
+ React.warn(someInvariant, message)

@gaearon
Copy link
Collaborator

gaearon commented Mar 21, 2019

Tbh our experience with warning / invariant APIs is people find them confusing (easy to mess up the condition, and you can't do args lazily without a build transform). We might be eventually migrating away from them in React as well.

@bvaughn
Copy link
Contributor Author

bvaughn commented Mar 21, 2019

Could we change the signature to be equal to existing APIs such as warning or invariant?

I've personally always found those signatures confusing. I think these methods being 1:1 with their console counterparts is going to be easier for open source users. More familiar.

@bvaughn
Copy link
Contributor Author

bvaughn commented Mar 21, 2019

In it's current form React.warn requires two levels of nesting to not appear in production.

Also to be clear, the idea is for these to be DEV-only warnings. So you would want some form of process.env.NODE_ENV check so they are removed entirely from production builds.

@eps1lon
Copy link
Collaborator

eps1lon commented Mar 21, 2019

I've personally always found those signatures confusing.

100% agreed here. I'm never comfortable once I have to negate boolean logic. To many moving parts.

More familiar.

I would disagree here looking at the download stats for warning and the number of dependents.

Also to be clear, the idea is for these to be DEV-only warnings. So you would want some form of process.env.NODE_ENV check so they are removed entirely from production builds.

That's where babel-plugin-transform-dev-warning comes into play. This would already work by switching

- import warning from 'warning';
+ import { warn as warning } from 'react';

if the signatures were the same.

But I understand if you want to use the opportunity to create a better API. We can still wrap them with a custom function that has the same API as warning

function warning(condition, ...reactWarnArgs) {
  if (!condition) {
    React.warn(...reactWarnArgs)
  }
}

@gaearon
Copy link
Collaborator

gaearon commented Mar 21, 2019

I would disagree here looking at the download stats for warning and the number of dependents.

A lot of this was just cargo cult from early on. I and other people saw React using warning, it seemed "smart" and we copied it, and others spread the pattern. Especially because at the time you would "deduplicate" the logic this way. (You can't anymore since flat bundles.) I don't think this pattern is actually that useful or anything.

@bvaughn
Copy link
Contributor Author

bvaughn commented Mar 21, 2019

I think wrapping (if you want to mimic the warning API) is a good solution 👍 I think the built-in API mirroring e.g. console.warn is going to be easiest to understand for the most people. (Regardless of how many people download warning - more people use console.warn 😁 )

if (__DEV__) {
const ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;

error = (...args) => {
Copy link
Collaborator

@sebmarkbage sebmarkbage Mar 21, 2019

Choose a reason for hiding this comment

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

Let's write out the ES5 code. I don't trust the Babel plugin to do a better job than we do. (And we can easily accidentally forget to transpile it and cause more trouble downstream.)

@oliviertassinari
Copy link
Contributor

oliviertassinari commented Mar 21, 2019

That's where babel-plugin-transform-dev-warning comes into play.

@eps1lon Maybe we should deprecate the babel plugin? If React is making the if branch explicit, adding an extra process.env.NODE_ENV !== "production" should be simple. The pull request description's issue could be written like this:

import { warn } from "react";

function List(props) {
  if ((props.innerTagName != null || props.outerTagName != null) && process.env.NODE_ENV !== "production") {
    warn(
      "The innerTagName and outerTagName props have been deprecated. " +
        "Please use the innerElementType and outerElementType props instead."
    );
  }

  // ...
}

@bvaughn
Copy link
Contributor Author

bvaughn commented Mar 21, 2019

The pull request description's issue could be written like this:

Wouldn't the result end up being something more like this?

import { warn } from "react";

function List(props) {
  // This check wouldn't be removed, so it would bloat the bundle.
  if (props.innerTagName != null || props.outerTagName != null) {
    if (process.env.NODE_ENV !== "production") {
      warn(
        "The innerTagName and outerTagName props have been deprecated. " +
          "Please use the innerElementType and outerElementType props instead."
      );
    }
  }

  // ...
}

@bvaughn bvaughn merged commit f161ee2 into facebook:master Mar 21, 2019
@bvaughn bvaughn deleted the warn-with-component-stack branch March 21, 2019 21:44
@Andarist
Copy link
Contributor

Env check should always be the first thing in the if statement - because as @bvaughn has noticed other conditions might not get removed properly if you put the env check non-first. In this case minifier wont assume in most cases that getters, such as props.innerTagName here, are side-effect free.

@diegohaz
Copy link

Will this print the error/warn multiple times if the component is re-rendered?

@Andarist
Copy link
Contributor

@diegohaz looking at the implementation - yes, it's just a wrapper around console.warn/error:
https://github.com/facebook/react/pull/15170/files#diff-5ddbfb03321128deb9f08b4daf40fe11R17

@bvaughn
Copy link
Contributor Author

bvaughn commented Mar 23, 2019

Will this print the error/warn multiple times if the component is re-rendered?

Yes. Up to you to do your own de-duping. I suggest using a WeakSet or something similar for this.

@mrmckeb
Copy link

mrmckeb commented Mar 24, 2019

Would this be slated for 16.9? Looks great!

@gaearon
Copy link
Collaborator

gaearon commented Mar 24, 2019

Yeah planned for 16.9

@sahrens
Copy link
Contributor

sahrens commented Jun 27, 2019

Awesome! I'm very excited to finally have something here. Did you consider including the "welcome to debugging in react" throw/catch for error? What about exposing a utility for getting the stack addendum itself?

@bvaughn
Copy link
Contributor Author

bvaughn commented Jun 27, 2019

Did you consider including the "welcome to debugging in react" throw/catch for error?

No, I don't think was considered, since we're just calling console.error rather than actually throwing something.

What about exposing a utility for getting the stack addendum itself?

This was considered briefly and TBH I don't remember the discussion super clearly. I think we wanted to avoid an API that might encourage people parsing the stack and building things on top of it. (You can obviously still intercept console method calls to get the stack if you really want to.)

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.