diff --git a/Libraries/Renderer/src/ReactVersion.js b/Libraries/Renderer/src/ReactVersion.js index 6800654f5d6d29..6bf2d3b1bba84c 100644 --- a/Libraries/Renderer/src/ReactVersion.js +++ b/Libraries/Renderer/src/ReactVersion.js @@ -11,4 +11,4 @@ 'use strict'; -module.exports = '16.0.0-alpha.4'; +module.exports = '16.0.0-alpha.6'; diff --git a/Libraries/Renderer/src/renderers/native/ReactNativeEventEmitter.js b/Libraries/Renderer/src/renderers/native/ReactNativeEventEmitter.js index 6cea86f9d6ae65..42b69aa1506635 100644 --- a/Libraries/Renderer/src/renderers/native/ReactNativeEventEmitter.js +++ b/Libraries/Renderer/src/renderers/native/ReactNativeEventEmitter.js @@ -102,11 +102,6 @@ var ReactNativeEventEmitter = { ) { var nativeEvent = nativeEventParam || EMPTY_NATIVE_EVENT; var inst = ReactNativeComponentTree.getInstanceFromNode(rootNodeID); - if (!inst) { - // If the original instance is already gone, we don't have to dispatch - // any events. - return; - } ReactGenericBatching.batchedUpdates(function() { ReactNativeEventEmitter.handleTopLevel( topLevelType, diff --git a/Libraries/Renderer/src/renderers/native/__tests__/ReactNativeEvents-test.js b/Libraries/Renderer/src/renderers/native/__tests__/ReactNativeEvents-test.js index 783f4bab164c57..56206149a75e06 100644 --- a/Libraries/Renderer/src/renderers/native/__tests__/ReactNativeEvents-test.js +++ b/Libraries/Renderer/src/renderers/native/__tests__/ReactNativeEvents-test.js @@ -267,3 +267,97 @@ it('handles when a responder is unmounted while a touch sequence is in progress' expect(getResponderId()).toBe('two'); expect(log).toEqual(['two responder start']); }); + +it('handles events without target', () => { + var EventEmitter = RCTEventEmitter.register.mock.calls[0][0]; + var View = createReactNativeComponentClass({ + validAttributes: {id: true}, + uiViewClassName: 'View', + }); + + function getViewById(id) { + return UIManager.createView.mock.calls.find( + args => args[3] && args[3].id === id, + )[0]; + } + + function getResponderId() { + const responder = ResponderEventPlugin._getResponder(); + if (responder === null) { + return null; + } + const props = typeof responder.tag === 'number' + ? responder.memoizedProps + : responder._currentElement.props; + return props ? props.id : null; + } + + var log = []; + + function render(renderFirstComponent) { + ReactNative.render( + + + {renderFirstComponent + ? log.push('one responder end')} + onResponderStart={() => log.push('one responder start')} + onStartShouldSetResponder={() => true} + /> + : null} + + + log.push('two responder end')} + onResponderStart={() => log.push('two responder start')} + onStartShouldSetResponder={() => true} + /> + + , + 1, + ); + } + + render(true); + + EventEmitter.receiveTouches( + 'topTouchStart', + [{target: getViewById('one'), identifier: 17}], + [0], + ); + + // Unmounting component 'one'. + render(false); + + EventEmitter.receiveTouches( + 'topTouchEnd', + [{target: getViewById('one'), identifier: 17}], + [0], + ); + + expect(getResponderId()).toBe(null); + + EventEmitter.receiveTouches( + 'topTouchStart', + [{target: getViewById('two'), identifier: 18}], + [0], + ); + + expect(getResponderId()).toBe('two'); + + EventEmitter.receiveTouches( + 'topTouchEnd', + [{target: getViewById('two'), identifier: 18}], + [0], + ); + + expect(getResponderId()).toBe(null); + + expect(log).toEqual([ + 'one responder start', + 'two responder start', + 'two responder end', + ]); +}); diff --git a/Libraries/Renderer/src/renderers/shared/fiber/ReactFiberErrorLogger.js b/Libraries/Renderer/src/renderers/shared/fiber/ReactFiberErrorLogger.js index 30678fc0826c78..4deb6549322a72 100644 --- a/Libraries/Renderer/src/renderers/shared/fiber/ReactFiberErrorLogger.js +++ b/Libraries/Renderer/src/renderers/shared/fiber/ReactFiberErrorLogger.js @@ -12,8 +12,13 @@ 'use strict'; +const emptyFunction = require('fbjs/lib/emptyFunction'); +const invariant = require('fbjs/lib/invariant'); + import type {CapturedError} from 'ReactFiberScheduler'; +let showDialog = emptyFunction; + function logCapturedError(capturedError: CapturedError): void { if (__DEV__) { const { @@ -80,6 +85,22 @@ function logCapturedError(capturedError: CapturedError): void { `React caught an error thrown by one of your components.\n\n${error.stack}`, ); } + + showDialog(capturedError); } +exports.injection = { + injectDialog(fn: (e: CapturedError) => void) { + invariant( + showDialog === emptyFunction, + 'The custom dialog was already injected.', + ); + invariant( + typeof fn === 'function', + 'Injected showDialog() must be a function.', + ); + showDialog = fn; + }, +}; + exports.logCapturedError = logCapturedError; diff --git a/Libraries/Renderer/src/renderers/shared/utils/ReactErrorUtils.js b/Libraries/Renderer/src/renderers/shared/utils/ReactErrorUtils.js index b3910a0dd8953d..2a08fb1124f18c 100644 --- a/Libraries/Renderer/src/renderers/shared/utils/ReactErrorUtils.js +++ b/Libraries/Renderer/src/renderers/shared/utils/ReactErrorUtils.js @@ -12,8 +12,70 @@ 'use strict'; +const invariant = require('fbjs/lib/invariant'); + let caughtError = null; +let invokeGuardedCallback = function(name, func, context, a, b, c, d, e, f) { + const funcArgs = Array.prototype.slice.call(arguments, 3); + try { + func.apply(context, funcArgs); + } catch (error) { + return error; + } + return null; +}; + +if (__DEV__) { + /** + * To help development we can get better devtools integration by simulating a + * real browser event. + */ + if ( + typeof window !== 'undefined' && + typeof window.dispatchEvent === 'function' && + typeof document !== 'undefined' && + typeof document.createEvent === 'function' + ) { + const fakeNode = document.createElement('react'); + let depth = 0; + + invokeGuardedCallback = function(name, func, context, a, b, c, d, e, f) { + depth++; + const thisDepth = depth; + const funcArgs = Array.prototype.slice.call(arguments, 3); + const boundFunc = function() { + func.apply(context, funcArgs); + }; + let fakeEventError = null; + const onFakeEventError = function(event) { + // Don't capture nested errors + if (depth === thisDepth) { + fakeEventError = event.error; + } + }; + const evtType = `react-${name ? name : 'invokeguardedcallback'}-${depth}`; + window.addEventListener('error', onFakeEventError); + fakeNode.addEventListener(evtType, boundFunc, false); + const evt = document.createEvent('Event'); + evt.initEvent(evtType, false, false); + fakeNode.dispatchEvent(evt); + fakeNode.removeEventListener(evtType, boundFunc, false); + window.removeEventListener('error', onFakeEventError); + depth--; + return fakeEventError; + }; + } +} + +let rethrowCaughtError = function() { + if (caughtError) { + const error = caughtError; + caughtError = null; + throw error; + } +}; + /** * Call a function while guarding against errors that happens within it. * Returns an error if it throws, otherwise null. @@ -24,6 +86,16 @@ let caughtError = null; * @param {...*} args Arguments for function */ const ReactErrorUtils = { + injection: { + injectErrorUtils(injectedErrorUtils: Object) { + invariant( + typeof injectedErrorUtils.invokeGuardedCallback === 'function', + 'Injected invokeGuardedCallback() must be a function.', + ); + invokeGuardedCallback = injectedErrorUtils.invokeGuardedCallback; + }, + }, + invokeGuardedCallback: function( name: string | null, func: (a: A, b: B, c: C, d: D, e: E, f: F) => void, @@ -35,13 +107,7 @@ const ReactErrorUtils = { e: E, f: F, ): Error | null { - const funcArgs = Array.prototype.slice.call(arguments, 3); - try { - func.apply(context, funcArgs); - } catch (error) { - return error; - } - return null; + return invokeGuardedCallback.apply(this, arguments); }, /** @@ -75,64 +141,8 @@ const ReactErrorUtils = { * we will rethrow to be handled by the top level error handler. */ rethrowCaughtError: function() { - if (caughtError) { - const error = caughtError; - caughtError = null; - throw error; - } + return rethrowCaughtError.apply(this, arguments); }, }; -if (__DEV__) { - /** - * To help development we can get better devtools integration by simulating a - * real browser event. - */ - if ( - typeof window !== 'undefined' && - typeof window.dispatchEvent === 'function' && - typeof document !== 'undefined' && - typeof document.createEvent === 'function' - ) { - const fakeNode = document.createElement('react'); - let depth = 0; - - ReactErrorUtils.invokeGuardedCallback = function( - name, - func, - context, - a, - b, - c, - d, - e, - f, - ) { - depth++; - const thisDepth = depth; - const funcArgs = Array.prototype.slice.call(arguments, 3); - const boundFunc = function() { - func.apply(context, funcArgs); - }; - let fakeEventError = null; - const onFakeEventError = function(event) { - // Don't capture nested errors - if (depth === thisDepth) { - fakeEventError = event.error; - } - }; - const evtType = `react-${name ? name : 'invokeguardedcallback'}-${depth}`; - window.addEventListener('error', onFakeEventError); - fakeNode.addEventListener(evtType, boundFunc, false); - const evt = document.createEvent('Event'); - evt.initEvent(evtType, false, false); - fakeNode.dispatchEvent(evt); - fakeNode.removeEventListener(evtType, boundFunc, false); - window.removeEventListener('error', onFakeEventError); - depth--; - return fakeEventError; - }; - } -} - module.exports = ReactErrorUtils; diff --git a/package.json b/package.json index fba4c5e4bb0a9e..43b74612e52ae7 100644 --- a/package.json +++ b/package.json @@ -128,7 +128,7 @@ "react-native": "local-cli/wrong-react-native.js" }, "peerDependencies": { - "react": "~16.0.0-alpha.4" + "react": "16.0.0-alpha.6" }, "dependencies": { "absolute-path": "^0.0.0", @@ -187,6 +187,9 @@ "plist": "^1.2.0", "pretty-format": "^4.2.1", "promise": "^7.1.1", + "react-addons-create-fragment": "15.5.0-alpha.0", + "react-addons-pure-render-mixin": "15.5.0-alpha.0", + "react-addons-shallow-compare": "15.5.0-alpha.0", "react-clone-referenced-element": "^1.0.1", "react-devtools-core": "^2.0.8", "react-timer-mixin": "^0.13.2", @@ -224,9 +227,9 @@ "jest-repl": "19.0.2", "jest-runtime": "19.0.2", "mock-fs": "^3.11.0", - "react": "~16.0.0-alpha.4", - "react-dom": "~16.0.0-alpha.4", - "react-test-renderer": "~16.0.0-alpha.3", + "react": "16.0.0-alpha.6", + "react-dom": "16.0.0-alpha.6", + "react-test-renderer": "16.0.0-alpha.6", "shelljs": "0.6.0", "sinon": "^2.0.0-pre.2" }