diff --git a/packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js b/packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js
index 367e55ed28d4d..af0111e4eb9c8 100644
--- a/packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js
+++ b/packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js
@@ -21,6 +21,7 @@ function createReactEventComponent(targetEventTypes, handleEvent) {
return {
$$typeof: Symbol.for('react.event_component'),
+ displayName: 'TestEventComponent',
props: null,
responder: testEventResponder,
};
diff --git a/packages/react-events/src/Drag.js b/packages/react-events/src/Drag.js
index cc741e29b5b32..54498760ff305 100644
--- a/packages/react-events/src/Drag.js
+++ b/packages/react-events/src/Drag.js
@@ -211,6 +211,7 @@ const DragResponder = {
export default {
$$typeof: REACT_EVENT_COMPONENT_TYPE,
+ displayName: 'Drag',
props: null,
responder: DragResponder,
};
diff --git a/packages/react-events/src/Focus.js b/packages/react-events/src/Focus.js
index c574f1e3a1f9b..e56379f8871dd 100644
--- a/packages/react-events/src/Focus.js
+++ b/packages/react-events/src/Focus.js
@@ -122,6 +122,7 @@ const FocusResponder = {
export default {
$$typeof: REACT_EVENT_COMPONENT_TYPE,
+ displayName: 'Focus',
props: null,
responder: FocusResponder,
};
diff --git a/packages/react-events/src/Hover.js b/packages/react-events/src/Hover.js
index 75992d246aa17..226430b838d09 100644
--- a/packages/react-events/src/Hover.js
+++ b/packages/react-events/src/Hover.js
@@ -204,6 +204,7 @@ const HoverResponder = {
export default {
$$typeof: REACT_EVENT_COMPONENT_TYPE,
+ displayName: 'Hover',
props: null,
responder: HoverResponder,
};
diff --git a/packages/react-events/src/Press.js b/packages/react-events/src/Press.js
index 7611d8660c185..b79c38758b9a8 100644
--- a/packages/react-events/src/Press.js
+++ b/packages/react-events/src/Press.js
@@ -395,6 +395,7 @@ const PressResponder = {
export default {
$$typeof: REACT_EVENT_COMPONENT_TYPE,
+ displayName: 'Press',
props: null,
responder: PressResponder,
};
diff --git a/packages/react-events/src/Swipe.js b/packages/react-events/src/Swipe.js
index c67b502316e74..85df2cca3f10e 100644
--- a/packages/react-events/src/Swipe.js
+++ b/packages/react-events/src/Swipe.js
@@ -239,6 +239,7 @@ const SwipeResponder = {
export default {
$$typeof: REACT_EVENT_COMPONENT_TYPE,
+ displayName: 'Swipe',
props: null,
responder: SwipeResponder,
};
diff --git a/packages/react-events/src/__tests__/Press-test.internal.js b/packages/react-events/src/__tests__/Press-test.internal.js
index 1505c1f3033a4..af3b93239e480 100644
--- a/packages/react-events/src/__tests__/Press-test.internal.js
+++ b/packages/react-events/src/__tests__/Press-test.internal.js
@@ -394,4 +394,8 @@ describe('Event responder: Press', () => {
expect(events).toEqual(['keydown', 'inner: onPress', 'outer: onPress']);
});
});
+
+ it('expect displayName to show up for event component', () => {
+ expect(Press.displayName).toBe('Press');
+ });
});
diff --git a/packages/react-reconciler/src/__tests__/ReactFiberEvents-test-internal.js b/packages/react-reconciler/src/__tests__/ReactFiberEvents-test-internal.js
index 25162397b0490..73dcc8d4a01e5 100644
--- a/packages/react-reconciler/src/__tests__/ReactFiberEvents-test-internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactFiberEvents-test-internal.js
@@ -29,6 +29,7 @@ const noOpResponder = {
function createReactEventComponent() {
return {
$$typeof: ReactSymbols.REACT_EVENT_COMPONENT_TYPE,
+ displayName: 'TestEventComponent',
props: null,
responder: noOpResponder,
};
@@ -37,6 +38,7 @@ function createReactEventComponent() {
function createReactEventTarget() {
return {
$$typeof: ReactSymbols.REACT_EVENT_TARGET_TYPE,
+ displayName: 'TestEventTarget',
type: Symbol.for('react.event_target.test'),
};
}
@@ -392,6 +394,54 @@ describe('ReactFiberEvents', () => {
'Warning: validateDOMNesting: React event targets must not have event components as children.',
);
});
+
+ it('should error with a component stack contains the names of the event components and event targets', () => {
+ let componentStackMessage;
+
+ function ErrorComponent() {
+ throw new Error('Failed!');
+ }
+
+ const Test = () => (
+
+
+
+
+
+
+
+ );
+
+ class Wrapper extends React.Component {
+ state = {
+ error: null,
+ };
+
+ componentDidCatch(error, errMessage) {
+ componentStackMessage = errMessage.componentStack;
+ this.setState({
+ error,
+ });
+ }
+
+ render() {
+ if (this.state.error) {
+ return null;
+ }
+ return ;
+ }
+ }
+
+ ReactNoop.render();
+ expect(Scheduler).toFlushWithoutYielding();
+
+ expect(componentStackMessage.includes('ErrorComponent')).toBe(true);
+ expect(componentStackMessage.includes('span')).toBe(true);
+ expect(componentStackMessage.includes('TestEventTarget')).toBe(true);
+ expect(componentStackMessage.includes('TestEventComponent')).toBe(true);
+ expect(componentStackMessage.includes('Test')).toBe(true);
+ expect(componentStackMessage.includes('Wrapper')).toBe(true);
+ });
});
describe('TestRenderer', () => {
@@ -570,7 +620,7 @@ describe('ReactFiberEvents', () => {
error: null,
};
- componentDidCatch(error) {
+ componentDidCatch(error, errStack) {
this.setState({
error,
});
@@ -734,6 +784,55 @@ describe('ReactFiberEvents', () => {
'Warning: validateDOMNesting: React event targets must not have event components as children.',
);
});
+
+ it('should error with a component stack contains the names of the event components and event targets', () => {
+ let componentStackMessage;
+
+ function ErrorComponent() {
+ throw new Error('Failed!');
+ }
+
+ const Test = () => (
+
+
+
+
+
+
+
+ );
+
+ class Wrapper extends React.Component {
+ state = {
+ error: null,
+ };
+
+ componentDidCatch(error, errMessage) {
+ componentStackMessage = errMessage.componentStack;
+ this.setState({
+ error,
+ });
+ }
+
+ render() {
+ if (this.state.error) {
+ return null;
+ }
+ return ;
+ }
+ }
+
+ const root = ReactTestRenderer.create(null);
+ root.update();
+ expect(Scheduler).toFlushWithoutYielding();
+
+ expect(componentStackMessage.includes('ErrorComponent')).toBe(true);
+ expect(componentStackMessage.includes('span')).toBe(true);
+ expect(componentStackMessage.includes('TestEventTarget')).toBe(true);
+ expect(componentStackMessage.includes('TestEventComponent')).toBe(true);
+ expect(componentStackMessage.includes('Test')).toBe(true);
+ expect(componentStackMessage.includes('Wrapper')).toBe(true);
+ });
});
describe('ReactDOM', () => {
@@ -1053,6 +1152,54 @@ describe('ReactFiberEvents', () => {
'Warning: validateDOMNesting: React event targets must not have event components as children.',
);
});
+
+ it('should error with a component stack contains the names of the event components and event targets', () => {
+ let componentStackMessage;
+
+ function ErrorComponent() {
+ throw new Error('Failed!');
+ }
+
+ const Test = () => (
+
+
+
+
+
+
+
+ );
+
+ class Wrapper extends React.Component {
+ state = {
+ error: null,
+ };
+
+ componentDidCatch(error, errMessage) {
+ componentStackMessage = errMessage.componentStack;
+ this.setState({
+ error,
+ });
+ }
+
+ render() {
+ if (this.state.error) {
+ return null;
+ }
+ return ;
+ }
+ }
+
+ const container = document.createElement('div');
+ ReactDOM.render(, container);
+
+ expect(componentStackMessage.includes('ErrorComponent')).toBe(true);
+ expect(componentStackMessage.includes('span')).toBe(true);
+ expect(componentStackMessage.includes('TestEventTarget')).toBe(true);
+ expect(componentStackMessage.includes('TestEventComponent')).toBe(true);
+ expect(componentStackMessage.includes('Test')).toBe(true);
+ expect(componentStackMessage.includes('Wrapper')).toBe(true);
+ });
});
describe('ReactDOMServer', () => {
diff --git a/packages/shared/ReactTypes.js b/packages/shared/ReactTypes.js
index cedd17d170a2a..52ce27f9dfb10 100644
--- a/packages/shared/ReactTypes.js
+++ b/packages/shared/ReactTypes.js
@@ -93,11 +93,13 @@ export type ReactEventResponder = {
export type ReactEventComponent = {|
$$typeof: Symbol | number,
+ displayName?: string,
props: null | Object,
responder: ReactEventResponder,
|};
export type ReactEventTarget = {|
$$typeof: Symbol | number,
+ displayName?: string,
type: Symbol | number,
|};
diff --git a/packages/shared/getComponentName.js b/packages/shared/getComponentName.js
index cbea431c8f7a8..cfa07d91e1d13 100644
--- a/packages/shared/getComponentName.js
+++ b/packages/shared/getComponentName.js
@@ -22,8 +22,12 @@ import {
REACT_STRICT_MODE_TYPE,
REACT_SUSPENSE_TYPE,
REACT_LAZY_TYPE,
+ REACT_EVENT_COMPONENT_TYPE,
+ REACT_EVENT_TARGET_TYPE,
+ REACT_EVENT_TARGET_TOUCH_HIT,
} from 'shared/ReactSymbols';
import {refineResolvedLazyComponent} from 'shared/ReactLazyComponent';
+import type {ReactEventComponent, ReactEventTarget} from 'shared/ReactTypes';
function getWrappedName(
outerType: mixed,
@@ -87,6 +91,25 @@ function getComponentName(type: mixed): string | null {
if (resolvedThenable) {
return getComponentName(resolvedThenable);
}
+ break;
+ }
+ case REACT_EVENT_COMPONENT_TYPE: {
+ const eventComponent = ((type: any): ReactEventComponent);
+ const displayName = eventComponent.displayName;
+ if (displayName !== undefined) {
+ return displayName;
+ }
+ break;
+ }
+ case REACT_EVENT_TARGET_TYPE: {
+ const eventTarget = ((type: any): ReactEventTarget);
+ if (eventTarget.type === REACT_EVENT_TARGET_TOUCH_HIT) {
+ return 'TouchHitTarget';
+ }
+ const displayName = eventTarget.displayName;
+ if (displayName !== undefined) {
+ return displayName;
+ }
}
}
}