-
Notifications
You must be signed in to change notification settings - Fork 47.4k
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
Changes from 3 commits
6f2849e
04341d4
c55be00
5429c6e
6bcd757
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 |
---|---|---|
|
@@ -49,6 +49,7 @@ import { | |
patch as patchConsole, | ||
registerRenderer as registerRendererWithConsole, | ||
} from './console'; | ||
import {isMemo, isForwardRef} from 'react-is'; | ||
|
||
import type {Fiber} from 'react-reconciler/src/ReactFiber'; | ||
import type { | ||
|
@@ -326,17 +327,29 @@ export function getInternalReactConstants( | |
SCOPE_SYMBOL_STRING, | ||
} = ReactSymbols; | ||
|
||
function resolveFiberType(type: any) { | ||
// 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; | ||
} | ||
if (isForwardRef(type)) { | ||
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. I don't think we should use Edit: This has already been addressed. |
||
return type.render; | ||
} | ||
// recursively resolving memo type in case of memo(forwardRef(Component)) | ||
if (isMemo(type)) { | ||
return resolveFiberType(type.type); | ||
} | ||
return type; | ||
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. Would be nice to add a new test or two to DevTools I can add that after merging the fix though :) |
||
} | ||
|
||
// NOTICE Keep in sync with shouldFilterFiber() and other get*ForFiber methods | ||
function getDisplayNameForFiber(fiber: Fiber): string | null { | ||
const {elementType, type, tag} = fiber; | ||
|
||
// This is to support lazy components with a Promise as the type. | ||
// see https://github.com/facebook/react/pull/13397 | ||
let resolvedType = type; | ||
if (typeof type === 'object' && type !== null) { | ||
if (typeof type.then === 'function') { | ||
resolvedType = type._reactResult; | ||
} | ||
resolvedType = resolveFiberType(type); | ||
} | ||
|
||
let resolvedContext: any = null; | ||
|
@@ -350,8 +363,7 @@ export function getInternalReactConstants( | |
return getDisplayName(resolvedType); | ||
case ForwardRef: | ||
return ( | ||
resolvedType.displayName || | ||
getDisplayName(resolvedType.render, 'Anonymous') | ||
resolvedType.displayName || getDisplayName(resolvedType, 'Anonymous') | ||
); | ||
case HostRoot: | ||
return null; | ||
|
@@ -366,7 +378,7 @@ export function getInternalReactConstants( | |
if (elementType.displayName) { | ||
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 commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. Tests added #17283 |
||
} | ||
case SuspenseComponent: | ||
return 'Suspense'; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -106,7 +106,9 @@ describe('ReactIs', () => { | |
|
||
it('should identify ref forwarding component', () => { | ||
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 commentThe 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 I think the one exception for this that makes sense may be portals, since the value returned by 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 |
||
expect(ReactIs.isForwardRef(<RefForwardingComponent />)).toBe(true); | ||
expect(ReactIs.isForwardRef({type: ReactIs.StrictMode})).toBe(false); | ||
expect(ReactIs.isForwardRef(<div />)).toBe(false); | ||
|
@@ -131,9 +133,10 @@ describe('ReactIs', () => { | |
|
||
it('should identify memo', () => { | ||
const Component = () => React.createElement('div'); | ||
const memoized = React.memo(Component); | ||
expect(ReactIs.typeOf(memoized)).toBe(ReactIs.Memo); | ||
expect(ReactIs.isMemo(memoized)).toBe(true); | ||
const Memoized = React.memo(Component); | ||
expect(ReactIs.typeOf(Memoized)).toBe(ReactIs.Memo); | ||
expect(ReactIs.typeOf(<Memoized />)).toBe(ReactIs.Element); | ||
wsmd marked this conversation as resolved.
Show resolved
Hide resolved
|
||
expect(ReactIs.isMemo(Memoized)).toBe(true); | ||
expect(ReactIs.isMemo(Component)).toBe(false); | ||
}); | ||
|
||
|
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.