From 9c7cf20dd56a42fe417e6caa264e75058bf92911 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?= Date: Thu, 16 Mar 2017 15:29:06 -0700 Subject: [PATCH] Ignore event listener extraction on numeric text components (#9194) In ReactDOM we don't dispatch events in the synthetic event system on to text nodes. We back up one to the element before dispatching. In React Native we don't do that. The iOS native side doesn't dispatch on the text nodes but only the parent. The Android native side however does dispatch on text nodes. We already covered this for strings, but current element in Stack can be a number if it is numeric text content. --- .../__tests__/ReactNativeEvents-test.js | 87 +++++++++++++++++++ .../shared/shared/event/EventPluginHub.js | 13 ++- 2 files changed, 93 insertions(+), 7 deletions(-) diff --git a/src/renderers/native/__tests__/ReactNativeEvents-test.js b/src/renderers/native/__tests__/ReactNativeEvents-test.js index c6d0085a59..783f4bab16 100644 --- a/src/renderers/native/__tests__/ReactNativeEvents-test.js +++ b/src/renderers/native/__tests__/ReactNativeEvents-test.js @@ -87,6 +87,93 @@ it('handles events', () => { ]); }); +it('handles events on text nodes', () => { + expect(RCTEventEmitter.register.mock.calls.length).toBe(1); + var EventEmitter = RCTEventEmitter.register.mock.calls[0][0]; + + var Text = createReactNativeComponentClass({ + validAttributes: {foo: true}, + uiViewClassName: 'Text', + }); + + class ContextHack extends React.Component { + static childContextTypes = {isInAParentText: React.PropTypes.bool}; + getChildContext() { + return {isInAParentText: true}; + } + render() { + return this.props.children; + } + } + + var log = []; + ReactNative.render( + + + log.push('string touchend')} + onTouchEndCapture={() => log.push('string touchend capture')} + onTouchStart={() => log.push('string touchstart')} + onTouchStartCapture={() => log.push('string touchstart capture')}> + Text Content + + log.push('number touchend')} + onTouchEndCapture={() => log.push('number touchend capture')} + onTouchStart={() => log.push('number touchstart')} + onTouchStartCapture={() => log.push('number touchstart capture')}> + {123} + + + , + 1, + ); + + expect(UIManager.createView.mock.calls.length).toBe(5); + + // Don't depend on the order of createView() calls. + // Stack creates views outside-in; fiber creates them inside-out. + var innerTagString = UIManager.createView.mock.calls.find( + args => args[3] && args[3].text === 'Text Content', + )[0]; + var innerTagNumber = UIManager.createView.mock.calls.find( + args => args[3] && args[3].text === '123', + )[0]; + + EventEmitter.receiveTouches( + 'topTouchStart', + [{target: innerTagString, identifier: 17}], + [0], + ); + EventEmitter.receiveTouches( + 'topTouchEnd', + [{target: innerTagString, identifier: 17}], + [0], + ); + + EventEmitter.receiveTouches( + 'topTouchStart', + [{target: innerTagNumber, identifier: 18}], + [0], + ); + EventEmitter.receiveTouches( + 'topTouchEnd', + [{target: innerTagNumber, identifier: 18}], + [0], + ); + + expect(log).toEqual([ + 'string touchstart capture', + 'string touchstart', + 'string touchend capture', + 'string touchend', + 'number touchstart capture', + 'number touchstart', + 'number touchend capture', + 'number touchend', + ]); +}); + it('handles when a responder is unmounted while a touch sequence is in progress', () => { var EventEmitter = RCTEventEmitter.register.mock.calls[0][0]; var View = createReactNativeComponentClass({ diff --git a/src/renderers/shared/shared/event/EventPluginHub.js b/src/renderers/shared/shared/event/EventPluginHub.js index 7ed1950708..ac723ff890 100644 --- a/src/renderers/shared/shared/event/EventPluginHub.js +++ b/src/renderers/shared/shared/event/EventPluginHub.js @@ -138,7 +138,10 @@ var EventPluginHub = { return null; } } else { - if (typeof inst._currentElement === 'string') { + const currentElement = inst._currentElement; + if ( + typeof currentElement === 'string' || typeof currentElement === 'number' + ) { // Text node, let it bubble through. return null; } @@ -146,14 +149,10 @@ var EventPluginHub = { // If the instance is already unmounted, we have no listeners. return null; } - const props = inst._currentElement.props; + const props = currentElement.props; listener = props[registrationName]; if ( - shouldPreventMouseEvent( - registrationName, - inst._currentElement.type, - props, - ) + shouldPreventMouseEvent(registrationName, currentElement.type, props) ) { return null; }