Skip to content

Commit a6f42c7

Browse files
author
Brian Vaughn
committed
Only anonymous inner functions of memo/forwardRef inherit displayName
1 parent b668b12 commit a6f42c7

File tree

4 files changed

+71
-6
lines changed

4 files changed

+71
-6
lines changed

packages/react-reconciler/src/__tests__/ReactMemo-test.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,24 @@ describe('memo', () => {
553553
);
554554
});
555555

556+
it('should pass displayName to an anonymous inner component so it shows up in component stacks', () => {
557+
const MemoComponent = React.memo(props => {
558+
return <div {...props} />;
559+
});
560+
MemoComponent.displayName = 'Memo';
561+
MemoComponent.propTypes = {
562+
required: PropTypes.string.isRequired,
563+
};
564+
565+
expect(() =>
566+
ReactNoop.render(<MemoComponent optional="foo" />),
567+
).toErrorDev(
568+
'Warning: Failed prop type: The prop `required` is marked as required in ' +
569+
'`Memo`, but its value is `undefined`.\n' +
570+
' in Memo (at **)',
571+
);
572+
});
573+
556574
it('should honor a outer displayName when wrapped component and memo component set displayName at the same time.', () => {
557575
function Component(props) {
558576
return <div {...props} />;

packages/react/src/ReactForwardRef.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,17 @@ export function forwardRef<Props, ElementType: React$ElementType>(
5757
},
5858
set: function(name) {
5959
ownName = name;
60+
61+
// The inner component shouldn't inherit this display name in most cases,
62+
// because the component may be used elsewhere.
63+
// But it's nice for anonymous functions to inherit the name,
64+
// so that our component-stack generation logic will display their frames.
65+
// An anonymous function generally suggests a pattern like:
66+
// React.forwardRef((props, ref) => {...});
67+
// This kind of inner function is not used elsewhere so the side effect is okay.
68+
if (!render.name && !render.displayName) {
69+
render.displayName = name;
70+
}
6071
},
6172
});
6273
}

packages/react/src/ReactMemo.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,17 @@ export function memo<Props>(
3737
},
3838
set: function(name) {
3939
ownName = name;
40+
41+
// The inner component shouldn't inherit this display name in most cases,
42+
// because the component may be used elsewhere.
43+
// But it's nice for anonymous functions to inherit the name,
44+
// so that our component-stack generation logic will display their frames.
45+
// An anonymous function generally suggests a pattern like:
46+
// React.memo((props) => {...});
47+
// This kind of inner function is not used elsewhere so the side effect is okay.
48+
if (!type.name && !type.displayName) {
49+
type.displayName = name;
50+
}
4051
},
4152
});
4253
}

packages/react/src/__tests__/forwardRef-test.js

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -250,10 +250,38 @@ describe('forwardRef', () => {
250250
it('should honor a displayName if set on the forwardRef wrapper in warnings', () => {
251251
const Component = props => <div {...props} />;
252252

253+
const RefForwardingComponent = React.forwardRef(function Inner(props, ref) {
254+
<Component {...props} forwardedRef={ref} />;
255+
});
256+
RefForwardingComponent.displayName = 'Custom';
257+
258+
RefForwardingComponent.propTypes = {
259+
optional: PropTypes.string,
260+
required: PropTypes.string.isRequired,
261+
};
262+
263+
RefForwardingComponent.defaultProps = {
264+
optional: 'default',
265+
};
266+
267+
const ref = React.createRef();
268+
269+
expect(() =>
270+
ReactNoop.render(<RefForwardingComponent ref={ref} optional="foo" />),
271+
).toErrorDev(
272+
'Warning: Failed prop type: The prop `required` is marked as required in ' +
273+
'`Custom`, but its value is `undefined`.\n' +
274+
' in Inner (at **)',
275+
);
276+
});
277+
278+
it('should pass displayName to an anonymous inner component so it shows up in component stacks', () => {
279+
const Component = props => <div {...props} />;
280+
253281
const RefForwardingComponent = React.forwardRef((props, ref) => (
254282
<Component {...props} forwardedRef={ref} />
255283
));
256-
RefForwardingComponent.displayName = 'Outer';
284+
RefForwardingComponent.displayName = 'Custom';
257285

258286
RefForwardingComponent.propTypes = {
259287
optional: PropTypes.string,
@@ -270,11 +298,8 @@ describe('forwardRef', () => {
270298
ReactNoop.render(<RefForwardingComponent ref={ref} optional="foo" />),
271299
).toErrorDev(
272300
'Warning: Failed prop type: The prop `required` is marked as required in ' +
273-
'`Outer`, but its value is `undefined`.',
274-
// There's no component stack in this warning because the inner function is anonymous.
275-
// If we wanted to support this (for the Error frames / source location)
276-
// we could do this by updating ReactComponentStackFrame.
277-
{withoutStack: true},
301+
'`Custom`, but its value is `undefined`.\n' +
302+
' in Custom (at **)',
278303
);
279304
});
280305

0 commit comments

Comments
 (0)