Fixed bug #875 - added exception handlers around calls to findDOMNode. We had previously assumed this call returned null if the node couldn't be found, but it turns out to throw an exception.

This commit is contained in:
Eric Traut 2018-10-27 11:10:07 -07:00
Родитель e8efbf656c
Коммит fe406d0a53
10 изменённых файлов: 228 добавлений и 183 удалений

106
package-lock.json сгенерированный
Просмотреть файл

@ -19,7 +19,7 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.3.18.tgz",
"integrity": "sha512-aWTvLHzKqbVWCiee8huwf5x7Ob4n4gxDwgJT/X31HqjGVZpeUeFeSFYH5Gvi5Dmm5HKF+s+dQkwa/nnEVVzzzg==",
"requires": {
"csstype": "2.5.6"
"csstype": "^2.2.0"
}
},
"@types/react-dom": {
@ -27,8 +27,8 @@
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.0.7.tgz",
"integrity": "sha512-vaq4vMaJOaNgFff1t3LnHYr6vRa09vRspMkmLdXtFZmO1fwDI2snP+dpOkwrtlU8UC8qsqemCu4RmVM2OLq/fA==",
"requires": {
"@types/node": "10.9.2",
"@types/react": "16.3.18"
"@types/node": "*",
"@types/react": "*"
}
},
"@types/react-native": {
@ -36,7 +36,7 @@
"resolved": "https://registry.npmjs.org/@types/react-native/-/react-native-0.56.12.tgz",
"integrity": "sha512-LrxtjunNc3p2hb3yPo4KCrzB4xxP14yqgrIhBIcHs+EK7w0m1mUqPKXUhQMbe0l/obZc4P+8q+ulXCWE47x0jw==",
"requires": {
"@types/react": "16.3.18"
"@types/react": "*"
}
},
"ansi-regex": {
@ -57,7 +57,7 @@
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"dev": true,
"requires": {
"sprintf-js": "1.0.3"
"sprintf-js": "~1.0.2"
}
},
"assert": {
@ -74,9 +74,9 @@
"integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=",
"dev": true,
"requires": {
"chalk": "1.1.3",
"esutils": "2.0.2",
"js-tokens": "3.0.2"
"chalk": "^1.1.3",
"esutils": "^2.0.2",
"js-tokens": "^3.0.2"
},
"dependencies": {
"chalk": {
@ -85,11 +85,11 @@
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"dev": true,
"requires": {
"ansi-styles": "2.2.1",
"escape-string-regexp": "1.0.5",
"has-ansi": "2.0.0",
"strip-ansi": "3.0.1",
"supports-color": "2.0.0"
"ansi-styles": "^2.2.1",
"escape-string-regexp": "^1.0.2",
"has-ansi": "^2.0.0",
"strip-ansi": "^3.0.0",
"supports-color": "^2.0.0"
}
},
"js-tokens": {
@ -112,7 +112,7 @@
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"requires": {
"balanced-match": "1.0.0",
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
@ -128,9 +128,9 @@
"integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
"dev": true,
"requires": {
"ansi-styles": "3.2.1",
"escape-string-regexp": "1.0.5",
"supports-color": "5.5.0"
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
},
"dependencies": {
"ansi-styles": {
@ -139,7 +139,7 @@
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "1.9.2"
"color-convert": "^1.9.0"
}
},
"supports-color": {
@ -148,7 +148,7 @@
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
"has-flag": "3.0.0"
"has-flag": "^3.0.0"
}
}
}
@ -221,12 +221,12 @@
"integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
"dev": true,
"requires": {
"fs.realpath": "1.0.0",
"inflight": "1.0.6",
"inherits": "2.0.1",
"minimatch": "3.0.4",
"once": "1.4.0",
"path-is-absolute": "1.0.1"
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"has-ansi": {
@ -235,7 +235,7 @@
"integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
"dev": true,
"requires": {
"ansi-regex": "2.1.1"
"ansi-regex": "^2.0.0"
}
},
"has-flag": {
@ -250,8 +250,8 @@
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"requires": {
"once": "1.4.0",
"wrappy": "1.0.2"
"once": "^1.3.0",
"wrappy": "1"
}
},
"inherits": {
@ -270,8 +270,8 @@
"integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==",
"dev": true,
"requires": {
"argparse": "1.0.10",
"esprima": "4.0.1"
"argparse": "^1.0.7",
"esprima": "^4.0.0"
}
},
"lodash": {
@ -284,7 +284,7 @@
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"requires": {
"js-tokens": "4.0.0"
"js-tokens": "^3.0.0 || ^4.0.0"
}
},
"minimatch": {
@ -293,7 +293,7 @@
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"requires": {
"brace-expansion": "1.1.11"
"brace-expansion": "^1.1.7"
}
},
"object-assign": {
@ -307,7 +307,7 @@
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"requires": {
"wrappy": "1.0.2"
"wrappy": "1"
}
},
"path-is-absolute": {
@ -327,8 +327,8 @@
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz",
"integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==",
"requires": {
"loose-envify": "1.4.0",
"object-assign": "4.1.1"
"loose-envify": "^1.3.1",
"object-assign": "^4.1.1"
}
},
"rebound": {
@ -342,7 +342,7 @@
"integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==",
"dev": true,
"requires": {
"path-parse": "1.0.6"
"path-parse": "^1.0.5"
}
},
"semver": {
@ -363,7 +363,7 @@
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"dev": true,
"requires": {
"ansi-regex": "2.1.1"
"ansi-regex": "^2.0.0"
}
},
"subscribableevent": {
@ -394,18 +394,18 @@
"integrity": "sha1-mPMMAurjzecAYgHkwzywi0hYHu0=",
"dev": true,
"requires": {
"babel-code-frame": "6.26.0",
"builtin-modules": "1.1.1",
"chalk": "2.4.1",
"commander": "2.17.1",
"diff": "3.5.0",
"glob": "7.1.2",
"js-yaml": "3.12.0",
"minimatch": "3.0.4",
"resolve": "1.8.1",
"semver": "5.5.1",
"tslib": "1.9.3",
"tsutils": "2.29.0"
"babel-code-frame": "^6.22.0",
"builtin-modules": "^1.1.1",
"chalk": "^2.3.0",
"commander": "^2.12.1",
"diff": "^3.2.0",
"glob": "^7.1.1",
"js-yaml": "^3.7.0",
"minimatch": "^3.0.4",
"resolve": "^1.3.2",
"semver": "^5.3.0",
"tslib": "^1.8.0",
"tsutils": "^2.27.2"
},
"dependencies": {
"tsutils": {
@ -414,7 +414,7 @@
"integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==",
"dev": true,
"requires": {
"tslib": "1.9.3"
"tslib": "^1.8.1"
}
}
}
@ -425,7 +425,7 @@
"integrity": "sha512-PDYjvpo0gN9IfMULwKk0KpVOPMhU6cNoT9VwCOLeDl/QS8v8W2yspRpFFuUS7/c5EIH/n8ApMi8TxJAz1tfFUA==",
"dev": true,
"requires": {
"tsutils": "2.28.0"
"tsutils": "^2.27.2 <2.29.0"
},
"dependencies": {
"tsutils": {
@ -434,7 +434,7 @@
"integrity": "sha512-bh5nAtW0tuhvOJnx1GLRn5ScraRLICGyJV5wJhtRWOLsxW70Kk5tZtpK3O/hW6LDnqKS9mlUMPZj9fEMJ0gxqA==",
"dev": true,
"requires": {
"tslib": "1.9.3"
"tslib": "^1.8.1"
}
}
}
@ -445,7 +445,7 @@
"integrity": "sha512-LjHBWR0vWAUHWdIAoTjoqi56Kz+FDKBgVEuL+gVPG/Pv7QW5IdaDDeK9Txlr6U0Cmckp5EgCIq1T25qe3J6hyw==",
"dev": true,
"requires": {
"tslib": "1.9.3"
"tslib": "^1.8.1"
}
},
"typescript": {

Просмотреть файл

@ -9,7 +9,6 @@
import * as PropTypes from 'prop-types';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { applyFocusableComponentMixin } from './utils/FocusManager';
import { Button as ButtonBase } from '../common/Interfaces';
@ -59,7 +58,7 @@ export class Button extends ButtonBase {
hasRxButtonAscendant: PropTypes.bool
};
private _isMounted = false;
private _mountedButton: HTMLButtonElement|null = null;
private _lastMouseDownEvent: Types.SyntheticEvent|undefined;
private _ignoreClick = false;
private _longPressTimer: number|undefined;
@ -92,6 +91,7 @@ export class Button extends ButtonBase {
// NOTE: We use tabIndex=0 to support focus.
return (
<button
ref={ this._onMount }
style={ this._getStyles() as any }
role={ ariaRole }
title={ this.props.title }
@ -122,41 +122,33 @@ export class Button extends ButtonBase {
}
componentDidMount() {
this._isMounted = true;
if (this.props.autoFocus) {
this.requestFocus();
}
}
componentWillUnmount() {
this._isMounted = false;
}
requestFocus() {
FocusArbitratorProvider.requestFocus(
this,
() => this.focus(),
() => this._isMounted
() => this._mountedButton !== null
);
}
focus() {
if (this._isMounted) {
const el = ReactDOM.findDOMNode(this) as HTMLButtonElement|null;
if (el) {
el.focus();
}
if (this._mountedButton) {
this._mountedButton.focus();
}
}
blur() {
if (this._isMounted) {
const el = ReactDOM.findDOMNode(this) as HTMLButtonElement|null;
if (el) {
el.blur();
if (this._mountedButton) {
this._mountedButton.blur();
}
}
private _onMount = (ref: HTMLButtonElement|null) => {
this._mountedButton = ref;
}
protected onClick = (e: Types.MouseEvent) => {

Просмотреть файл

@ -8,7 +8,6 @@
*/
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import * as PropTypes from 'prop-types';
import { CSSProperties } from 'react';
@ -56,7 +55,7 @@ export class Link extends React.Component<Types.LinkProps, Types.Stateless> {
context!: LinkContext;
private _isMounted = false;
private _mountedAnchor: HTMLAnchorElement|null = null;
private _longPressTimer: number|undefined;
render() {
@ -66,6 +65,7 @@ export class Link extends React.Component<Types.LinkProps, Types.Stateless> {
// See: https://mathiasbynens.github.io/rel-noopener/
return (
<a
ref={ this._onMount }
style={ this._getStyles() }
title={ this.props.title }
href={ this.props.url }
@ -86,44 +86,36 @@ export class Link extends React.Component<Types.LinkProps, Types.Stateless> {
}
componentDidMount() {
this._isMounted = true;
if (this.props.autoFocus) {
this.requestFocus();
}
}
componentWillUnmount() {
this._isMounted = false;
}
requestFocus() {
FocusArbitratorProvider.requestFocus(
this,
() => this.focus(),
() => this._isMounted
() => this._mountedAnchor !== null
);
}
focus() {
if (this._isMounted) {
const el = ReactDOM.findDOMNode(this) as HTMLAnchorElement|null;
if (el) {
el.focus();
}
if (this._mountedAnchor) {
this._mountedAnchor.focus();
}
}
blur() {
if (this._isMounted) {
const el = ReactDOM.findDOMNode(this) as HTMLAnchorElement|null;
if (el) {
el.blur();
}
if (this._mountedAnchor) {
this._mountedAnchor.blur();
}
}
_getStyles(): CSSProperties {
private _onMount = (ref: HTMLAnchorElement|null) => {
this._mountedAnchor = ref;
}
private _getStyles(): CSSProperties {
// There's no way in HTML to properly handle numberOfLines > 1,
// but we can correctly handle the common case where numberOfLines is 1.
const ellipsisStyles = this.props.numberOfLines === 1 ? _styles.ellipsis : {};

Просмотреть файл

@ -85,7 +85,7 @@ if (typeof document !== 'undefined') {
const style = document.createElement('style');
style.type = 'text/css';
style.appendChild(document.createTextNode(defaultBoxSizing));
document.head.appendChild(style);
document.head!.appendChild(style);
}
export interface MainViewContext {
@ -387,9 +387,14 @@ export class RootView extends React.Component<RootViewProps, RootViewState> {
}
private _determineIfClickOnElement(elementReference: React.Component<any, any>, eventSource: Element|null|undefined): boolean {
try {
const element = ReactDOM.findDOMNode(elementReference) as HTMLElement|null;
const isClickOnElement = !!element && !!eventSource && element.contains(eventSource);
return isClickOnElement;
} catch {
// Exception is due to race condition with unmounting.
return false;
}
}
private _onMouseDownCapture = (e: MouseEvent) => {
@ -600,7 +605,12 @@ export class RootView extends React.Component<RootViewProps, RootViewState> {
return;
}
let anchor = ReactDOM.findDOMNode(anchorComponent) as HTMLElement|null;
let anchor: HTMLElement|null = null;
try {
anchor = ReactDOM.findDOMNode(anchorComponent) as HTMLElement|null;
} catch {
anchor = null;
}
// If the anchor has disappeared, dismiss the popup.
if (!anchor) {

Просмотреть файл

@ -8,7 +8,6 @@
*/
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import * as _ from './utils/lodashMini';
import * as RX from '../common/Interfaces';
@ -233,9 +232,8 @@ export class ScrollView extends ViewBase<RX.Types.ScrollViewProps, RX.Types.Stat
this._customScrollbar = undefined;
}
let element = ReactDOM.findDOMNode(this) as HTMLElement|null;
if (element) {
this._customScrollbar = new CustomScrollbar(element);
if (this._mountedComponent) {
this._customScrollbar = new CustomScrollbar(this._mountedComponent);
const horizontalHidden = (props.horizontal && props.showsHorizontalScrollIndicator === false);
const verticalHidden = (props.vertical && props.showsVerticalScrollIndicator === false);
this._customScrollbar.init({

Просмотреть файл

@ -9,7 +9,6 @@
import * as PropTypes from 'prop-types';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { FocusArbitratorProvider } from '../common/utils/AutoFocusHelper';
import { Text as TextBase, Types } from '../common/Interfaces';
@ -25,7 +24,7 @@ if (typeof document !== 'undefined') {
const style = document.createElement('style');
style.type = 'text/css';
style.appendChild(document.createTextNode(textAsPseudoElement));
document.head.appendChild(style);
document.head!.appendChild(style);
}
const _styles = {
@ -66,7 +65,7 @@ export class Text extends TextBase {
isRxParentAText: PropTypes.bool.isRequired
};
private _isMounted = false;
private _mountedText: HTMLDivElement|null = null;
getChildContext() {
// Let descendant Types components know that their nearest Types ancestor is an Types.Text.
@ -86,6 +85,7 @@ export class Text extends TextBase {
if (this.props.selectable || typeof this.props.children !== 'string') {
return (
<div
ref={ this._onMount }
style={ this._getStyles() as any }
aria-hidden={ isAriaHidden }
onClick={ this.props.onPress }
@ -102,6 +102,7 @@ export class Text extends TextBase {
// will be displayed as pseudo element.
return (
<div
ref={ this._onMount }
style={ this._getStyles() as any }
aria-hidden={ isAriaHidden }
onClick={ this.props.onPress }
@ -115,18 +116,16 @@ export class Text extends TextBase {
}
componentDidMount() {
this._isMounted = true;
if (this.props.autoFocus) {
this.requestFocus();
}
}
componentWillUnmount() {
this._isMounted = false;
private _onMount = (ref: HTMLDivElement|null) => {
this._mountedText = ref;
}
_getStyles(): Types.TextStyleRuleSet {
private _getStyles(): Types.TextStyleRuleSet {
// There's no way in HTML to properly handle numberOfLines > 1,
// but we can correctly handle the common case where numberOfLines is 1.
let combinedStyles = Styles.combine([this.props.numberOfLines === 1 ?
@ -156,11 +155,8 @@ export class Text extends TextBase {
}
blur() {
if (this._isMounted) {
const el = ReactDOM.findDOMNode(this) as HTMLDivElement|null;
if (el) {
el.blur();
}
if (this._mountedText) {
this._mountedText.blur();
}
}
@ -168,16 +164,13 @@ export class Text extends TextBase {
FocusArbitratorProvider.requestFocus(
this,
() => this.focus(),
() => this._isMounted
() => this._mountedText !== null
);
}
focus() {
if (this._isMounted) {
const el = ReactDOM.findDOMNode(this) as HTMLDivElement|null;
if (el) {
el.focus();
}
if (this._mountedText) {
this._mountedText.focus();
}
}

Просмотреть файл

@ -28,8 +28,13 @@ export class UserInterface extends RX.UserInterface {
SyncTasks.Promise<RX.Types.LayoutInfo> {
let deferred = SyncTasks.Defer<RX.Types.LayoutInfo>();
let componentDomNode: HTMLElement | null = null;
const componentDomNode = ReactDOM.findDOMNode(component) as HTMLElement|null;
try {
componentDomNode = ReactDOM.findDOMNode(component) as HTMLElement | null;
} catch {
// Component is no longer mounted.
}
if (!componentDomNode) {
deferred.reject('measureLayoutRelativeToWindow failed');
@ -51,9 +56,15 @@ export class UserInterface extends RX.UserInterface {
ancestor: React.Component<any, any>) : SyncTasks.Promise<RX.Types.LayoutInfo> {
let deferred = SyncTasks.Defer<RX.Types.LayoutInfo>();
let componentDomNode: HTMLElement | null = null;
let ancestorDomNode: HTMLElement | null = null;
const componentDomNode = ReactDOM.findDOMNode(component) as HTMLElement|null;
const ancestorDomNode = ReactDOM.findDOMNode(ancestor) as HTMLElement|null;
try {
componentDomNode = ReactDOM.findDOMNode(component) as HTMLElement | null;
ancestorDomNode = ReactDOM.findDOMNode(ancestor) as HTMLElement | null;
} catch {
// Components are no longer mounted.
}
if (!componentDomNode || !ancestorDomNode) {
deferred.reject('measureLayoutRelativeToAncestor failed');

Просмотреть файл

@ -68,7 +68,7 @@ if (typeof document !== 'undefined') {
const style = document.createElement('style');
style.type = 'text/css';
style.appendChild(document.createTextNode(ignorePointerEvents));
head.appendChild(style);
head!.appendChild(style);
}
export interface ViewContext {
@ -157,7 +157,13 @@ export class View extends ViewBase<Types.ViewProps, Types.Stateless> {
let initResizer = (key: 'grow' | 'shrink', ref: any) => {
const cur: HTMLElement | undefined = this._resizeDetectorNodes[key];
const element = ReactDOM.findDOMNode(ref) as HTMLElement|null;
let element: HTMLElement|null = null;
try {
element = ReactDOM.findDOMNode(ref) as HTMLElement|null;
} catch {
// Swallow exception due to component unmount race condition.
}
if (cur) {
delete this._resizeDetectorNodes[key];
@ -257,7 +263,12 @@ export class View extends ViewBase<Types.ViewProps, Types.Stateless> {
if (!this._isMounted) {
return null;
}
try {
return ReactDOM.findDOMNode(this) as HTMLElement|null;
} catch {
// Handle exception due to potential unmount race condition.
return null;
}
}
private _isHidden(): boolean {
@ -467,10 +478,14 @@ export class View extends ViewBase<Types.ViewProps, Types.Stateless> {
blur() {
if (this._isMounted) {
try {
const el = ReactDOM.findDOMNode(this) as HTMLDivElement|null;
if (el) {
el.blur();
}
} catch {
// Handle exception due to potential unmount race condition.
}
}
}
@ -484,10 +499,14 @@ export class View extends ViewBase<Types.ViewProps, Types.Stateless> {
focus() {
if (this._isMounted) {
try {
const el = ReactDOM.findDOMNode(this) as HTMLDivElement|null;
if (el) {
el.focus();
}
} catch {
// Handle exception due to potential unmount race condition.
}
}
}
}

Просмотреть файл

@ -35,6 +35,7 @@ export class AnimateListEdits extends React.Component<AnimateListEditsProps, Typ
let delay = 0;
if (edits.removed.length > 0 && this.props.animateChildLeave) {
edits.removed.forEach(function (move) {
try {
let domNode = ReactDOM.findDOMNode(move.element) as HTMLElement|null;
if (domNode) {
domNode.style.transform = 'translateY(' + -move.topDelta + 'px)';
@ -49,6 +50,9 @@ export class AnimateListEdits extends React.Component<AnimateListEditsProps, Typ
timing: 'linear'
}], animationCompleted);
}
} catch {
// Exception probably due to race condition in unmounting. Ignore.
}
});
delay += 75;
}
@ -56,6 +60,8 @@ export class AnimateListEdits extends React.Component<AnimateListEditsProps, Typ
if (edits.moved.length > 0 && this.props.animateChildMove) {
edits.moved.forEach(function (move) {
counter++;
try {
let domNode = ReactDOM.findDOMNode(move.element) as HTMLElement|null;
if (domNode) {
executeTransition(domNode, [{
@ -67,6 +73,9 @@ export class AnimateListEdits extends React.Component<AnimateListEditsProps, Typ
timing: 'ease-out'
}], animationCompleted);
}
} catch {
// Exception probably due to race condition in unmounting. Ignore.
}
});
}
delay += 75;
@ -74,6 +83,8 @@ export class AnimateListEdits extends React.Component<AnimateListEditsProps, Typ
if (edits.added.length > 0 && this.props.animateChildEnter) {
edits.added.forEach(function (move) {
counter++;
try {
let domNode = ReactDOM.findDOMNode(move.element) as HTMLElement|null;
if (domNode) {
executeTransition(domNode, [{
@ -85,6 +96,9 @@ export class AnimateListEdits extends React.Component<AnimateListEditsProps, Typ
timing: 'linear'
}], animationCompleted);
}
} catch {
// Exception probably due to race condition in unmounting. Ignore.
}
});
}
animationCompleted();

Просмотреть файл

@ -93,27 +93,39 @@ export class FocusManager extends FocusManagerBase {
});
}
protected /* static */ addFocusListenerOnComponent(component: FocusableComponentInternal, onFocus: () => void): void {
protected addFocusListenerOnComponent(component: FocusableComponentInternal, onFocus: () => void): void {
try {
const el = ReactDOM.findDOMNode(component) as HTMLElement | null;
if (el) {
el.addEventListener('focus', onFocus);
}
} catch {
// Swallow exception due to component unmount race condition.
}
}
protected /* static */ removeFocusListenerFromComponent(component: FocusableComponentInternal, onFocus: () => void): void {
protected removeFocusListenerFromComponent(component: FocusableComponentInternal, onFocus: () => void): void {
try {
const el = ReactDOM.findDOMNode(component) as HTMLElement|null;
if (el) {
el.removeEventListener('focus', onFocus);
}
} catch {
// Swallow exception due to component unmount race condition.
}
}
protected /* static */ focusComponent(component: FocusableComponentInternal): boolean {
protected focusComponent(component: FocusableComponentInternal): boolean {
try {
const el = ReactDOM.findDOMNode(component) as HTMLElement|null;
if (el && el.focus) {
FocusManager.setLastFocusedProgrammatically(el);
el.focus();
return true;
}
} catch {
// Swallow exception due to component unmount race condition.
}
return false;
}
@ -367,10 +379,14 @@ export function applyFocusableComponentMixin(Component: any, isConditionallyFocu
if (origFocus) {
Component.prototype.focus = function () {
try {
const el = ReactDOM.findDOMNode(this) as HTMLElement|null;
if (el) {
FocusManager.setLastFocusedProgrammatically(el);
}
} catch {
// Swallow exception due to component unmount race condition.
}
origFocus.apply(this, arguments);
};