react: Bug: Native Component Stacks don't respect function "displayName" in Firefox

Edit See this comment for the key part of what’s being reported/discussed on this issue: https://github.com/facebook/react/issues/22315#issuecomment-920061727

Original bug report below

As of #18561 component stacks are generated from native stack frames. This is problematic with HOCs that inherit from the input component in order to change its behavior. The somewhat popular @risingstack/react-easy-state package is one example of such a component. While it does assign a displayName, the new Native Component Stacks appear to ignore this. Instead, components wrapped in view() (from react-easy-state) are always shown with the name of the wrapper class, i.e., ReactiveComp or ReactiveClassComp.

This is especially catastrophic in the case of react-easy-state, where one is supposed to wrap essentially all components in the entire codebase in the view() HOC. The result is that component stacks become unusable for debugging.

Is there perhaps a way to work around this (e.g. disable native component stacks, or some new way to explicitly provide a component name like displayName)?

React version: 17.0.1

Steps To Reproduce

  1. Apply a HOC that uses inheritance (i.e., inherits from the component instead of wrapping it in JSX) to a component.
  2. The component will always be named ReactiveComp or ReactiveClassComp in component stack traces.

Link to code example: https://codesandbox.io/s/rough-tdd-wqepe?file=/src/App.tsx

The current behavior

Something went wrong.
Error: sorry

BadGuy@https://7ww9j.csb.app/src/App.tsx:21:9
ErrorBoundary@https://7ww9j.csb.app/src/App.tsx:33:5
div
App
Something went wrong.
Error: sorry

ReactiveComp@https://7ww9j.csb.app/node_modules/@risingstack/react-easy-state/dist/es.es6.js:62:53
ErrorBoundary@https://7ww9j.csb.app/src/App.tsx:33:5
div
App

Note how the component wrapped in view() is shown as ReactiveComp instead of either the function name or the explicitly assigned displayName.

The expected behavior

The name of the ReactiveComp wrapper should never appear in component stacks.

About this issue

  • Original URL
  • State: open
  • Created 3 years ago
  • Comments: 19 (8 by maintainers)

Most upvoted comments

Looks like this issue has been inactive for awhile - if you’d prefer I open a new issue, let me know.

The following issue still exists:

Just to be clear, displayName is broken (at least for me) in all browsers. The problem looks like this: Native Component Stacks appear to just ignore displayName right now. Instead, it always prints the native function name.

From reading the underlying React Source code, this behavior appears to be intentional, with the idea being native component stacks will give you the most accurate information for debugging. I agree with this to a point, but as noted earlier in the thread, this is very problematic for Higher Order Components which want to preserve the name of the component they’re wrapping.

In fact, this is such a primary use case that it is covered specifically in the React docs: https://reactjs.org/docs/higher-order-components.html#convention-wrap-the-display-name-for-easy-debugging

As it stands currently, that documentation is not accurate, because defining displayName doesn’t do anything.

This is all independent of alternate mechanisms such as the previously mentioned defineProperty hack. Regardless of whether this hack works or not, the lack of support for displayName should be considered a bug, in my opinion, until the documentation is updated and/or a new convention is provided for how to make higher order components debuggable.

Accordingly, I would at least petition to drop “in Firefox” from this issue title, as the displayName issue is browser independent - it is only the workaround hack that is related to Firefox.

My personal suggestion is to override a portion of the stack frame with the displayName if it is set. This would require only a slight alteration to the code introduced by PR #22477 mentioned above, by adding the following else if clause:

                if (fn.displayName && _frame.includes('<anonymous>')) {
                  _frame = _frame.replace('<anonymous>', fn.displayName);
                } else if (fn.displayName) {
                  _frame = _frame.replace(fn.name, fn.displayName);
                }

I am not certain if this is robust enough for general use, but it solved the problem for me and is at least a step in the right direction.

Given the age of this issue, it seems like it won’t be fixed any time soon. However, I did find a workaround!

To recap, the problem is that given a snippet like this:

function higherOrderComponent(wrapped) {
  const wrappedComponent = (props) => {
    return wrapped(props);
  };

  return wrappedComponent
}

the closure will simply be named wrappedComponent and so the original name of the wrapped component (wrapped.name) is lost. While the most convenient solution here would undoubtedly be if React had a way to just override that name, we have to assume for now that this feature is not coming back.

So we need a way to set the actual name (and not just a shadowed “name” property like the hack for Chrome) of our closure to whatever wrapped.name says. It seems impossible - after all, the closure is named by the variable, and variable names are source code literals. So we can’t just create a variable with a dynamic name, can we? In fact, we can do just that:

const wrappedComponent = { [wrapped.name](props) {
    return wrapped(props);
} }[wrapped.name];

Also works for class components:

const WrappedClassComp = { [Wrapped.name]: class extends Wrapped {
    // ...
} }[Wrapped.name];

Implemented in react-easy-state here: https://github.com/RisingStack/react-easy-state/commit/458e4bb8b9367a082f80d9ce15a45d58b06c7333

You can Object.defineProperty instead for name. This works: https://codesandbox.io/s/tender-easley-vck0q?file=/src/App.tsx

function BadGuy() {
  throw Error("sorry");
}
Object.defineProperty(BadGuy, "name", { value: "BadGuyDisplayName" });

Okay! Glad we’re on the same page. 😃

I’m on-call for DevTools this week so I don’t really have the bandwidth to dig in any further, but I think we have enough info now for this to be discussed/decided.

I’m not positive but I don’t think what you’re looking for is possible (reading the displayName for the “native stack”) because we use Error to construct these stacks (so they’ll be in the exact same format) and Error doesn’t care about the displayName convention.