diff --git a/packages/react-dom/src/__tests__/ReactComponent-test.js b/packages/react-dom/src/__tests__/ReactComponent-test.js
index 0a63915429c2a..c892b3debeccd 100644
--- a/packages/react-dom/src/__tests__/ReactComponent-test.js
+++ b/packages/react-dom/src/__tests__/ReactComponent-test.js
@@ -145,7 +145,7 @@ describe('ReactComponent', () => {
ReactTestUtils.renderIntoDocument(} />);
});
- it('should support new-style refs', () => {
+ it('should support callback-style refs', () => {
var innerObj = {};
var outerObj = {};
@@ -185,6 +185,49 @@ describe('ReactComponent', () => {
expect(mounted).toBe(true);
});
+ it('should support object-style refs', () => {
+ var innerObj = {};
+ var outerObj = {};
+
+ class Wrapper extends React.Component {
+ getObject = () => {
+ return this.props.object;
+ };
+
+ render() {
+ return
{this.props.children}
;
+ }
+ }
+
+ var mounted = false;
+
+ class Component extends React.Component {
+ constructor() {
+ super();
+ this.innerRef = React.createRef();
+ this.outerRef = React.createRef();
+ }
+ render() {
+ var inner = ;
+ var outer = (
+
+ {inner}
+
+ );
+ return outer;
+ }
+
+ componentDidMount() {
+ expect(this.innerRef.value.getObject()).toEqual(innerObj);
+ expect(this.outerRef.value.getObject()).toEqual(outerObj);
+ mounted = true;
+ }
+ }
+
+ ReactTestUtils.renderIntoDocument();
+ expect(mounted).toBe(true);
+ });
+
it('should support new-style refs with mixed-up owners', () => {
class Wrapper extends React.Component {
getTitle = () => {
diff --git a/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.js b/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.js
index 01b2b33f3431b..a0835d2256845 100644
--- a/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.js
+++ b/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.js
@@ -937,7 +937,7 @@ describe('ReactErrorBoundaries', () => {
expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
});
- it('resets refs if mounting aborts', () => {
+ it('resets callback refs if mounting aborts', () => {
function childRef(x) {
log.push('Child ref is set to ' + x);
}
@@ -981,6 +981,44 @@ describe('ReactErrorBoundaries', () => {
]);
});
+ it('resets object refs if mounting aborts', () => {
+ let childRef = React.createRef();
+ let errorMessageRef = React.createRef();
+
+ var container = document.createElement('div');
+ ReactDOM.render(
+
+
+
+ ,
+ container,
+ );
+ expect(container.textContent).toBe('Caught an error: Hello.');
+ expect(log).toEqual([
+ 'ErrorBoundary constructor',
+ 'ErrorBoundary componentWillMount',
+ 'ErrorBoundary render success',
+ 'BrokenRender constructor',
+ 'BrokenRender componentWillMount',
+ 'BrokenRender render [!]',
+ // Handle error:
+ // Finish mounting with null children
+ 'ErrorBoundary componentDidMount',
+ // Handle the error
+ 'ErrorBoundary componentDidCatch',
+ // Render the error message
+ 'ErrorBoundary componentWillUpdate',
+ 'ErrorBoundary render error',
+ 'ErrorBoundary componentDidUpdate',
+ ]);
+ expect(errorMessageRef.value.toString()).toEqual('[object HTMLDivElement]');
+
+ log.length = 0;
+ ReactDOM.unmountComponentAtNode(container);
+ expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
+ expect(errorMessageRef.value).toEqual(null);
+ });
+
it('successfully mounts if no error occurs', () => {
var container = document.createElement('div');
ReactDOM.render(
diff --git a/packages/react-reconciler/src/ReactChildFiber.js b/packages/react-reconciler/src/ReactChildFiber.js
index 8f88719e3b9e3..abec1ae39856d 100644
--- a/packages/react-reconciler/src/ReactChildFiber.js
+++ b/packages/react-reconciler/src/ReactChildFiber.js
@@ -123,7 +123,11 @@ function getIteratorFn(maybeIterable: ?any): ?() => ?Iterator<*> {
function coerceRef(current: Fiber | null, element: ReactElement) {
let mixedRef = element.ref;
- if (mixedRef !== null && typeof mixedRef !== 'function') {
+ if (
+ mixedRef !== null &&
+ typeof mixedRef !== 'function' &&
+ typeof mixedRef !== 'object'
+ ) {
if (element._owner) {
const owner: ?Fiber = (element._owner: any);
let inst;
diff --git a/packages/react-reconciler/src/ReactFiber.js b/packages/react-reconciler/src/ReactFiber.js
index af2ce6478ea90..5cd4226c5cba2 100644
--- a/packages/react-reconciler/src/ReactFiber.js
+++ b/packages/react-reconciler/src/ReactFiber.js
@@ -8,6 +8,7 @@
import type {ReactElement, Source} from 'shared/ReactElementType';
import type {
+ RefObject,
ReactCall,
ReactFragment,
ReactPortal,
@@ -95,7 +96,7 @@ export type Fiber = {|
// The ref last used to attach this node.
// I'll avoid adding an owner field for prod and model that as functions.
- ref: null | (((handle: mixed) => void) & {_stringRef: ?string}),
+ ref: null | (((handle: mixed) => void) & {_stringRef: ?string}) | RefObject,
// Input is the data coming into process this fiber. Arguments. Props.
pendingProps: any, // This type will be more specific once we overload the tag.
diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.js b/packages/react-reconciler/src/ReactFiberCommitWork.js
index 68b990c1d23be..5d148c0d1bded 100644
--- a/packages/react-reconciler/src/ReactFiberCommitWork.js
+++ b/packages/react-reconciler/src/ReactFiberCommitWork.js
@@ -73,18 +73,22 @@ export default function(
function safelyDetachRef(current: Fiber) {
const ref = current.ref;
if (ref !== null) {
- if (__DEV__) {
- invokeGuardedCallback(null, ref, null, null);
- if (hasCaughtError()) {
- const refError = clearCaughtError();
- captureError(current, refError);
+ if (typeof ref === 'function') {
+ if (__DEV__) {
+ invokeGuardedCallback(null, ref, null, null);
+ if (hasCaughtError()) {
+ const refError = clearCaughtError();
+ captureError(current, refError);
+ }
+ } else {
+ try {
+ ref(null);
+ } catch (refError) {
+ captureError(current, refError);
+ }
}
} else {
- try {
- ref(null);
- } catch (refError) {
- captureError(current, refError);
- }
+ ref.value = null;
}
}
}
@@ -162,12 +166,18 @@ export default function(
const ref = finishedWork.ref;
if (ref !== null) {
const instance = finishedWork.stateNode;
+ let instanceToUse;
switch (finishedWork.tag) {
case HostComponent:
- ref(getPublicInstance(instance));
+ instanceToUse = getPublicInstance(instance);
break;
default:
- ref(instance);
+ instanceToUse = instance;
+ }
+ if (typeof ref === 'function') {
+ ref(instanceToUse);
+ } else {
+ ref.value = instanceToUse;
}
}
}
@@ -175,7 +185,11 @@ export default function(
function commitDetachRef(current: Fiber) {
const currentRef = current.ref;
if (currentRef !== null) {
- currentRef(null);
+ if (typeof currentRef === 'function') {
+ currentRef(null);
+ } else {
+ currentRef.value = null;
+ }
}
}
diff --git a/packages/react/src/React.js b/packages/react/src/React.js
index f25181ea80394..4cf9b648b3eb2 100644
--- a/packages/react/src/React.js
+++ b/packages/react/src/React.js
@@ -9,6 +9,7 @@ import assign from 'object-assign';
import ReactVersion from 'shared/ReactVersion';
import {enableReactFragment} from 'shared/ReactFeatureFlags';
+import {createRef} from './ReactCreateRef';
import {Component, PureComponent, AsyncComponent} from './ReactBaseClasses';
import {forEach, map, count, toArray, only} from './ReactChildren';
import ReactCurrentOwner from './ReactCurrentOwner';
@@ -31,7 +32,7 @@ const REACT_FRAGMENT_TYPE =
Symbol.for('react.fragment')) ||
0xeacb;
-var React = {
+const React = {
Children: {
map,
forEach,
@@ -40,6 +41,7 @@ var React = {
only,
},
+ createRef,
Component,
PureComponent,
unstable_AsyncComponent: AsyncComponent,
diff --git a/packages/react/src/ReactCreateRef.js b/packages/react/src/ReactCreateRef.js
new file mode 100644
index 0000000000000..8af60100e64d7
--- /dev/null
+++ b/packages/react/src/ReactCreateRef.js
@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) 2013-present, Facebook, Inc.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ * @flow
+ */
+
+import type {RefObject} from 'shared/ReactTypes';
+
+// an immutable object with a single mutable value
+export function createRef(): RefObject {
+ const refObject = {
+ value: null,
+ };
+ if (__DEV__) {
+ Object.seal(refObject);
+ }
+ return refObject;
+}
diff --git a/packages/shared/ReactTypes.js b/packages/shared/ReactTypes.js
index 4efa9a3851dd6..1da1d4e666a1a 100644
--- a/packages/shared/ReactTypes.js
+++ b/packages/shared/ReactTypes.js
@@ -45,3 +45,7 @@ export type ReactPortal = {
// TODO: figure out the API for cross-renderer implementation.
implementation: any,
};
+
+export type RefObject = {|
+ value: any,
+|};