-
Notifications
You must be signed in to change notification settings - Fork 50.2k
Fix devtools displaying Anonymous for memo of ref-forwarding components #17274
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
Fix devtools displaying Anonymous for memo of ref-forwarding components #17274
Conversation
|
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. Latest deployment of this branch, based on commit 6bcd757:
|
0591323 to
fb63b81
Compare
…f memo recursively Resolving the fiber type of memo recursively before passing it to getDisplayName will prevent it from displaying "Anonymous" as displayName for components wrapped with both memo and forwardRef: memo(forwardRef(Component))
fb63b81 to
c55be00
Compare
| // This is to support lazy components with a Promise as the type. | ||
| // see https://github.com/facebook/react/pull/13397 | ||
| if (typeof type.then === 'function') { | ||
| return type._reactResult; | ||
| } |
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.
I couldn't find any references to _reactResult in the codebase. Is this still relevant? Is it safe to remove?
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.
I think it has sense been renamed to _result now that you mention it.
Someone can follow up with that in a separate PR.
cc @acdlite
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.
I don't think this codepath is even used by DevTools anymore. By the time React DevTools gets called for the lazy component, it just gets the e.g. function.
| if (typeof type.then === 'function') { | ||
| return type._reactResult; | ||
| } | ||
| if (isForwardRef(type)) { |
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.
I don't think we should use react-is for this. Partly because the renderer already knows enough information to make this determination using the Fiber's tag and partly because react-is is meant to work with React elements and the renderer has a Fiber with a "type" (which is not an element).
Edit: This has already been addressed.
| if (isMemo(type)) { | ||
| return resolveFiberType(type.type); | ||
| } | ||
| return type; |
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.
Would be nice to add a new test or two to DevTools store-test for the types that were showing up as anonymous before that no longer are, to guard against a regression.
I can add that after merging the fix though :)
| const RefForwardingComponent = React.forwardRef((props, ref) => null); | ||
| expect(ReactIs.typeOf(RefForwardingComponent)).toBe(ReactIs.ForwardRef); | ||
| expect(ReactIs.typeOf(<RefForwardingComponent />)).toBe(ReactIs.ForwardRef); | ||
| expect(ReactIs.isForwardRef(RefForwardingComponent)).toBe(true); |
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.
Hm. This change seems to mirror the current behavior of memo components, so I understand it from that aspect, but I don't think it's right. The typeOf method is meant to look at React element types (e.g. <MyComponent /> or React.createElement(MyComponent)) not component types (e.g. MyComponent).
I think the one exception for this that makes sense may be portals, since the value returned by React.createPortal() is meant to be rendered directly (and not wrapped in an element).
I'm going to file a separate PR for this particular issue since I don't think it's strictly related to this PR: #17278
| return elementType.displayName; | ||
| } else { | ||
| return getDisplayName(type, 'Anonymous'); | ||
| return getDisplayName(resolvedType, 'Anonymous'); |
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.
I had been thinking of suggesting a different approach that just kind of localized the check:
diff --git a/packages/react-devtools-shared/src/backend/renderer.js b/packages/react-devtools-shared/src/backend/renderer.js
index 761c78935..284183f9f 100644
--- a/packages/react-devtools-shared/src/backend/renderer.js
+++ b/packages/react-devtools-shared/src/backend/renderer.js
@@ -365,6 +365,9 @@ export function getInternalReactConstants(
case SimpleMemoComponent:
if (elementType.displayName) {
return elementType.displayName;
+ } else if (type.type != null && type.type.render != null) {
+ // Special case of a forwardRef() nested in a memo()
+ return getDisplayName(type.type.render, 'Anonymous');
} else {
return getDisplayName(type, 'Anonymous');
}I don't know that I really have a preference between the two approaches I guess. 😄 Yours is fine~
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.
Tests added #17283
| // This is to support lazy components with a Promise as the type. | ||
| // see https://github.com/facebook/react/pull/13397 | ||
| if (typeof type.then === 'function') { | ||
| return type._reactResult; | ||
| } |
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.
I think it has sense been renamed to _result now that you mention it.
Someone can follow up with that in a separate PR.
cc @acdlite
Resolves #16722
This PR addresses the following issues:
react-devtools-sharednot displaying the proper displayName formemo(forwardRef(C))(Fixed in Fix react-is memo and lazy type checks #17278 🎉)react-is'stypeOfnot accounting forforwardRefcomponents/functions. It's currently accounting forforwardRefelements only.Problem: devtools not displaying the proper displayName for
memo(forwardRef(C))I encountered an issue with React dev-tools where it no longer infers the proper display name of a component wrapped with both
memoandforwardRef. The display name in this case is shown as "Anonymous".Consider the following example:
When I render
<MemoizedRefForwarding />, the React devtools shows the following:Notice how the
Memocomponent is displayed as "Anonymous"What I would expect is the following (taken after using the attempted fix):
The
Memocomponent is now properly displayed as "MemoizedRefForwarding"Solution
I traced down the problem and found that
react-devtools-sharedhas a shared helper functiongetDisplayNamethat expects a function. ThisgetDisplayNamefunction is being used bygetDisplayNameForFiberinbackend/renderer.js.While this works for most fibers with their
typeas the component function itself,forwardRefandmemohave a slightly differenttype.In the case of
memo(forwardRef(C)), thetypeof the memo element is theforwardRefobject, not the component function itself (whichgetDisplayNameexpects), so we need to extract that type off offorwardRef.I solved this problem by adding a
resolveFiberTypethat will attempt to "resolve" the type value, if the type of the fiber node happens to be an object, before passing it togetDisplayName. Since this is doing recursion it will covermemo(C),forwardRef(C), andmemo(forwardRef(C)).I'm not quite sure if that's the best possible way to approach this problem since it's my first time contributing to the React codebase 😬 so I'm not super familiar with it yet. I'd love some direction if there's a better way of handling this.
Problem (2) -react-is'stypeOfnot accounting forforwardRefHOCsOutdated Comment (Issue was resolved in in #17278)
To fix the devtools issue mentioned above, I wanted to use `react-is`'s helpers. Specifically, `isMemo`, and `isForwardRef`.
Looks like there's an outstanding issue with both of
ReactIs.isForwardRefandReactIs.typeOfwhere they don't respect aforwardRefHOC. It currently only works with elements created from afowardRefcomponent.I'm not sure if this is by design or intentional, but it does seem inconsistent with how
isMemoworks.I made a 1-line change to
react-isin this PR so that it respectsforwardRefHOCs (the actual components/functions) not just the elements. Are there any pitfalls to know?