Skip to content

Commit 34780da

Browse files
awearygaearon
authored andcommitted
Warn early for non-functional event listeners (#10453)
* Add early warning for non-functional event listeners * Use functional listeners in ReactDOMComponent test To avoid triggering the non-functional event listener component * spy on console.error in non-function EventPluginHub test This should warn from ReactDOMFiberComponent, but we just ignore it here * Remove redundant check for listener * Add expectation for non-functional listener warning in EventPluginHub * Hoist listener typeof check in hot paths * Include stack addendum in non-functional listener warning * Make it pretty * Remove it.onnly from ReactDOMFiber test * Fix message * Update expected message * Change invariant message to match the new style * Fix remaining warning
1 parent 6ab2869 commit 34780da

File tree

5 files changed

+57
-4
lines changed

5 files changed

+57
-4
lines changed

src/renderers/__tests__/EventPluginHub-test.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,33 @@ jest.mock('isEventSupported');
1616
describe('EventPluginHub', () => {
1717
var React;
1818
var ReactTestUtils;
19+
var ReactDOMFeatureFlags;
1920

2021
beforeEach(() => {
2122
jest.resetModules();
2223
React = require('react');
2324
ReactTestUtils = require('react-dom/test-utils');
25+
ReactDOMFeatureFlags = require('ReactDOMFeatureFlags');
2426
});
2527

2628
it('should prevent non-function listeners, at dispatch', () => {
29+
spyOn(console, 'error');
2730
var node = ReactTestUtils.renderIntoDocument(
2831
<div onClick="not a function" />,
2932
);
3033
expect(function() {
3134
ReactTestUtils.SimulateNative.click(node);
3235
}).toThrowError(
33-
'Expected onClick listener to be a function, instead got type string',
36+
'Expected `onClick` listener to be a function, instead got a value of `string` type.',
3437
);
38+
if (ReactDOMFeatureFlags.useFiber) {
39+
expectDev(console.error.calls.count()).toBe(1);
40+
expectDev(console.error.calls.argsFor(0)[0]).toContain(
41+
'Expected `onClick` listener to be a function, instead got a value of `string` type.',
42+
);
43+
} else {
44+
expectDev(console.error.calls.count()).toBe(0);
45+
}
3546
});
3647

3748
it('should not prevent null listeners, at dispatch', () => {

src/renderers/dom/fiber/ReactDOMFiberComponent.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ var setTextContent = require('setTextContent');
3434

3535
if (__DEV__) {
3636
var warning = require('fbjs/lib/warning');
37+
var {getCurrentFiberStackAddendum} = require('ReactDebugCurrentFiber');
3738
var ReactDOMInvalidARIAHook = require('ReactDOMInvalidARIAHook');
3839
var ReactDOMNullInputValuePropHook = require('ReactDOMNullInputValuePropHook');
3940
var ReactDOMUnknownPropertyHook = require('ReactDOMUnknownPropertyHook');
@@ -118,6 +119,16 @@ if (__DEV__) {
118119
warning(false, 'Extra attributes from the server: %s', names);
119120
};
120121

122+
var warnForInvalidEventListener = function(registrationName, listener) {
123+
warning(
124+
false,
125+
'Expected `%s` listener to be a function, instead got a value of `%s` type.%s',
126+
registrationName,
127+
typeof listener,
128+
getCurrentFiberStackAddendum(),
129+
);
130+
};
131+
121132
var testDocument;
122133
// Parse the HTML and read it back to normalize the HTML string so that it
123134
// can be used for comparison.
@@ -223,6 +234,9 @@ function setInitialDOMProperties(
223234
// Noop
224235
} else if (registrationNameModules.hasOwnProperty(propKey)) {
225236
if (nextProp) {
237+
if (__DEV__ && typeof nextProp !== 'function') {
238+
warnForInvalidEventListener(propKey, nextProp);
239+
}
226240
ensureListeningTo(rootContainerElement, propKey);
227241
}
228242
} else if (isCustomComponentTag) {
@@ -695,6 +709,9 @@ var ReactDOMFiberComponent = {
695709
} else if (registrationNameModules.hasOwnProperty(propKey)) {
696710
if (nextProp) {
697711
// We eagerly listen to this even though we haven't committed yet.
712+
if (__DEV__ && typeof nextProp !== 'function') {
713+
warnForInvalidEventListener(propKey, nextProp);
714+
}
698715
ensureListeningTo(rootContainerElement, propKey);
699716
}
700717
if (!updatePayload && lastProp !== nextProp) {
@@ -934,6 +951,9 @@ var ReactDOMFiberComponent = {
934951
}
935952
}
936953
} else if (registrationNameModules.hasOwnProperty(propKey)) {
954+
if (__DEV__ && typeof nextProp !== 'function') {
955+
warnForInvalidEventListener(propKey, nextProp);
956+
}
937957
if (nextProp) {
938958
ensureListeningTo(rootContainerElement, propKey);
939959
}

src/renderers/dom/fiber/__tests__/ReactDOMFiber-test.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ var ReactTestUtils = require('react-dom/test-utils');
1818
var PropTypes = require('prop-types');
1919

2020
describe('ReactDOMFiber', () => {
21+
function normalizeCodeLocInfo(str) {
22+
return str && str.replace(/\(at .+?:\d+\)/g, '(at **)');
23+
}
24+
2125
var container;
2226
var ReactFeatureFlags;
2327

@@ -913,6 +917,24 @@ describe('ReactDOMFiber', () => {
913917
]);
914918
});
915919

920+
it('should warn for non-functional event listeners', () => {
921+
spyOn(console, 'error');
922+
class Example extends React.Component {
923+
render() {
924+
return <div onClick="woops" />;
925+
}
926+
}
927+
ReactDOM.render(<Example />, container);
928+
expectDev(console.error.calls.count()).toBe(1);
929+
expectDev(
930+
normalizeCodeLocInfo(console.error.calls.argsFor(0)[0]),
931+
).toContain(
932+
'Expected `onClick` listener to be a function, instead got a value of `string` type.\n' +
933+
' in div (at **)\n' +
934+
' in Example (at **)',
935+
);
936+
});
937+
916938
it('should not update event handlers until commit', () => {
917939
let ops = [];
918940
const handlerA = () => ops.push('A');

src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1690,8 +1690,8 @@ describe('ReactDOMComponent', () => {
16901690
ReactTestUtils.renderIntoDocument(
16911691
<div className="foo1">
16921692
<div class="foo2" />
1693-
<div onClick="foo3" />
1694-
<div onclick="foo4" />
1693+
<div onClick={() => {}} />
1694+
<div onclick={() => {}} />
16951695
<div className="foo5" />
16961696
<div className="foo6" />
16971697
</div>,

src/renderers/shared/shared/event/EventPluginHub.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ var EventPluginHub = {
163163

164164
invariant(
165165
!listener || typeof listener === 'function',
166-
'Expected %s listener to be a function, instead got type %s',
166+
'Expected `%s` listener to be a function, instead got a value of `%s` type.',
167167
registrationName,
168168
typeof listener,
169169
);

0 commit comments

Comments
 (0)