Skip to content

Commit

Permalink
React sync (16 beta 6 plus addons)
Browse files Browse the repository at this point in the history
Reviewed By: sebmarkbage

Differential Revision: D4775005

fbshipit-source-id: c50d099dc3d01c10e122c56f11bd990a2b1e81d1
  • Loading branch information
Brian Vaughn authored and facebook-github-bot committed Mar 28, 2017
1 parent 92d985f commit 6f9447e
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 74 deletions.
2 changes: 1 addition & 1 deletion Libraries/Renderer/src/ReactVersion.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@

'use strict';

module.exports = '16.0.0-alpha.4';
module.exports = '16.0.0-alpha.6';
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<View id="parent">
<View key={1}>
{renderFirstComponent
? <View
id="one"
onResponderEnd={() => log.push('one responder end')}
onResponderStart={() => log.push('one responder start')}
onStartShouldSetResponder={() => true}
/>
: null}
</View>
<View key={2}>
<View
id="two"
onResponderEnd={() => log.push('two responder end')}
onResponderStart={() => log.push('two responder start')}
onStartShouldSetResponder={() => true}
/>
</View>
</View>,
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',
]);
});
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
138 changes: 74 additions & 64 deletions Libraries/Renderer/src/renderers/shared/utils/ReactErrorUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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<A, B, C, D, E, F, Context>(
name: string | null,
func: (a: A, b: B, c: C, d: D, e: E, f: F) => void,
Expand All @@ -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);
},

/**
Expand Down Expand Up @@ -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;
11 changes: 7 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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"
}
Expand Down

8 comments on commit 6f9447e

@janicduplessis
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bvaughn Are these addons actually needed? I removed the addons export that have been deprecated for a while a few days ago.

@bvaughn
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@janicduplessis Oh, great catch! If you've removed that export then no, I don't think they're needed any longer. I added them when syncing recently b'c that export would otherwise have been broken.

@janicduplessis
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I figured it would be better to just remove them since we are removing them from react too, they were there only for backwards compat with a deprecation warning.

@bvaughn
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Sounds good to me

@janicduplessis
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wanna take care of removing it?

@bvaughn
Copy link
Contributor

@bvaughn bvaughn commented on 6f9447e Mar 31, 2017 via email

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@janicduplessis
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, not really time sensitive, I only noticed it because it produces some unmet peer dep warnings. I can PR it later today, just wanted to coordinate so we don't both end up doing it :)

@bvaughn
Copy link
Contributor

@bvaughn bvaughn commented on 6f9447e Mar 31, 2017 via email

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.