@samitouri / QOS-React / commits / 8dc8f88d5a

Adds createRef() as per RFC (#12162)

* Adds createRef() as per RFC

Dominic Gannaway committed Feb 6, 2018 at 20:19 UTC 8dc8f88d5ae9fb96934ba43e3842b5dcf4074afd
9 files changed +146 -21
packages/react-dom/src/__tests__/ReactComponent-test.js
+44 -1
@@ -162,7 +162,7 @@ describe('ReactComponent', () => {
162 ReactTestUtils.renderIntoDocument(<Parent child={<span />} />);
163 });
164
165 - it('should support new-style refs', () => {
165 + it('should support callback-style refs', () => {
166 const innerObj = {};
167 const outerObj = {};
168
@@ -202,6 +202,49 @@ describe('ReactComponent', () => {
202 expect(mounted).toBe(true);
203 });
204
205 + it('should support object-style refs', () => {
206 + const innerObj = {};
207 + const outerObj = {};
208 +
209 + class Wrapper extends React.Component {
210 + getObject = () => {
211 + return this.props.object;
212 + };
213 +
214 + render() {
215 + return <div>{this.props.children}</div>;
216 + }
217 + }
218 +
219 + let mounted = false;
220 +
221 + class Component extends React.Component {
222 + constructor() {
223 + super();
224 + this.innerRef = React.createRef();
225 + this.outerRef = React.createRef();
226 + }
227 + render() {
228 + const inner = <Wrapper object={innerObj} ref={this.innerRef} />;
229 + const outer = (
230 + <Wrapper object={outerObj} ref={this.outerRef}>
231 + {inner}
232 + </Wrapper>
233 + );
234 + return outer;
235 + }
236 +
237 + componentDidMount() {
238 + expect(this.innerRef.value.getObject()).toEqual(innerObj);
239 + expect(this.outerRef.value.getObject()).toEqual(outerObj);
240 + mounted = true;
241 + }
242 + }
243 +
244 + ReactTestUtils.renderIntoDocument(<Component />);
245 + expect(mounted).toBe(true);
246 + });
247 +
248 it('should support new-style refs with mixed-up owners', () => {
249 class Wrapper extends React.Component {
250 getTitle = () => {
packages/react-dom/src/__tests__/ReactErrorBoundaries-test.js
+39 -1
@@ -937,7 +937,7 @@ describe('ReactErrorBoundaries', () => {
937 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
938 });
939
940 - it('resets refs if mounting aborts', () => {
940 + it('resets callback refs if mounting aborts', () => {
941 function childRef(x) {
942 log.push('Child ref is set to ' + x);
943 }
@@ -981,6 +981,44 @@ describe('ReactErrorBoundaries', () => {
981 ]);
982 });
983
984 + it('resets object refs if mounting aborts', () => {
985 + let childRef = React.createRef();
986 + let errorMessageRef = React.createRef();
987 +
988 + const container = document.createElement('div');
989 + ReactDOM.render(
990 + <ErrorBoundary errorMessageRef={errorMessageRef}>
991 + <div ref={childRef} />
992 + <BrokenRender />
993 + </ErrorBoundary>,
994 + container,
995 + );
996 + expect(container.textContent).toBe('Caught an error: Hello.');
997 + expect(log).toEqual([
998 + 'ErrorBoundary constructor',
999 + 'ErrorBoundary componentWillMount',
1000 + 'ErrorBoundary render success',
1001 + 'BrokenRender constructor',
1002 + 'BrokenRender componentWillMount',
1003 + 'BrokenRender render [!]',
1004 + // Handle error:
1005 + // Finish mounting with null children
1006 + 'ErrorBoundary componentDidMount',
1007 + // Handle the error
1008 + 'ErrorBoundary componentDidCatch',
1009 + // Render the error message
1010 + 'ErrorBoundary componentWillUpdate',
1011 + 'ErrorBoundary render error',
1012 + 'ErrorBoundary componentDidUpdate',
1013 + ]);
1014 + expect(errorMessageRef.value.toString()).toEqual('[object HTMLDivElement]');
1015 +
1016 + log.length = 0;
1017 + ReactDOM.unmountComponentAtNode(container);
1018 + expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
1019 + expect(errorMessageRef.value).toEqual(null);
1020 + });
1021 +
1022 it('successfully mounts if no error occurs', () => {
1023 const container = document.createElement('div');
1024 ReactDOM.render(
packages/react-reconciler/src/ReactChildFiber.js
+6 -2
@@ -104,7 +104,11 @@ function coerceRef(
104 element: ReactElement,
105 ) {
106 let mixedRef = element.ref;
107 - if (mixedRef !== null && typeof mixedRef !== 'function') {
107 + if (
108 + mixedRef !== null &&
109 + typeof mixedRef !== 'function' &&
110 + typeof mixedRef !== 'object'
111 + ) {
112 if (__DEV__) {
113 if (returnFiber.mode & StrictMode) {
114 const componentName = getComponentName(returnFiber) || 'Component';
@@ -113,7 +117,7 @@ function coerceRef(
117 false,
118 'A string ref, "%s", has been found within a strict mode tree. ' +
119 'String refs are a source of potential bugs and should be avoided. ' +
116 - 'We recommend using a ref callback instead.' +
120 + 'We recommend using createRef() instead.' +
121 '\n%s' +
122 '\n\nLearn more about using refs safely here:' +
123 '\nhttps://fb.me/react-strict-mode-string-ref',
packages/react-reconciler/src/ReactFiber.js
+2 -2
@@ -7,7 +7,7 @@
7 */
8
9 import type {ReactElement, Source} from 'shared/ReactElementType';
10 -import type {ReactPortal} from 'shared/ReactTypes';
10 +import type {ReactPortal, RefObject} from 'shared/ReactTypes';
11 import type {TypeOfWork} from 'shared/ReactTypeOfWork';
12 import type {TypeOfMode} from './ReactTypeOfMode';
13 import type {TypeOfSideEffect} from 'shared/ReactTypeOfSideEffect';
@@ -107,7 +107,7 @@ export type Fiber = {|
107
108 // The ref last used to attach this node.
109 // I'll avoid adding an owner field for prod and model that as functions.
110 - ref: null | (((handle: mixed) => void) & {_stringRef: ?string}),
110 + ref: null | (((handle: mixed) => void) & {_stringRef: ?string}) | RefObject,
111
112 // Input is the data coming into process this fiber. Arguments. Props.
113 pendingProps: any, // This type will be more specific once we overload the tag.
packages/react-reconciler/src/ReactFiberCommitWork.js
+27 -13
@@ -77,18 +77,22 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
77 function safelyDetachRef(current: Fiber) {
78 const ref = current.ref;
79 if (ref !== null) {
80 - if (__DEV__) {
81 - invokeGuardedCallback(null, ref, null, null);
82 - if (hasCaughtError()) {
83 - const refError = clearCaughtError();
84 - captureError(current, refError);
80 + if (typeof ref === 'function') {
81 + if (__DEV__) {
82 + invokeGuardedCallback(null, ref, null, null);
83 + if (hasCaughtError()) {
84 + const refError = clearCaughtError();
85 + captureError(current, refError);
86 + }
87 + } else {
88 + try {
89 + ref(null);
90 + } catch (refError) {
91 + captureError(current, refError);
92 + }
93 }
94 } else {
87 - try {
88 - ref(null);
89 - } catch (refError) {
90 - captureError(current, refError);
91 - }
95 + ref.value = null;
96 }
97 }
98 }
@@ -175,12 +179,18 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
179 const ref = finishedWork.ref;
180 if (ref !== null) {
181 const instance = finishedWork.stateNode;
182 + let instanceToUse;
183 switch (finishedWork.tag) {
184 case HostComponent:
180 - ref(getPublicInstance(instance));
185 + instanceToUse = getPublicInstance(instance);
186 break;
187 default:
183 - ref(instance);
188 + instanceToUse = instance;
189 + }
190 + if (typeof ref === 'function') {
191 + ref(instanceToUse);
192 + } else {
193 + ref.value = instanceToUse;
194 }
195 }
196 }
@@ -188,7 +198,11 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
198 function commitDetachRef(current: Fiber) {
199 const currentRef = current.ref;
200 if (currentRef !== null) {
191 - currentRef(null);
201 + if (typeof currentRef === 'function') {
202 + currentRef(null);
203 + } else {
204 + currentRef.value = null;
205 + }
206 }
207 }
208
packages/react/src/React.js
+2
@@ -14,6 +14,7 @@ import {
14 } from 'shared/ReactSymbols';
15
16 import {Component, PureComponent} from './ReactBaseClasses';
17 +import {createRef} from './ReactCreateRef';
18 import {forEach, map, count, toArray, only} from './ReactChildren';
19 import ReactCurrentOwner from './ReactCurrentOwner';
20 import {
@@ -39,6 +40,7 @@ const React = {
40 only,
41 },
42
43 + createRef,
44 Component,
45 PureComponent,
46
packages/react/src/ReactCreateRef.js new
+20
@@ -0,0 +1,20 @@
1 +/**
2 + * Copyright (c) 2013-present, Facebook, Inc.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + * @flow
7 + */
8 +
9 +import type {RefObject} from 'shared/ReactTypes';
10 +
11 +// an immutable object with a single mutable value
12 +export function createRef(): RefObject {
13 + const refObject = {
14 + value: null,
15 + };
16 + if (__DEV__) {
17 + Object.seal(refObject);
18 + }
19 + return refObject;
20 +}
packages/react/src/__tests__/ReactStrictMode-test.internal.js
+2 -2
@@ -778,7 +778,7 @@ describe('ReactStrictMode', () => {
778 }).toWarnDev(
779 'Warning: A string ref, "somestring", has been found within a strict mode tree. ' +
780 'String refs are a source of potential bugs and should be avoided. ' +
781 - 'We recommend using a ref callback instead.\n\n' +
781 + 'We recommend using createRef() instead.\n\n' +
782 ' in OuterComponent (at **)\n\n' +
783 'Learn more about using refs safely here:\n' +
784 'https://fb.me/react-strict-mode-string-ref',
@@ -819,7 +819,7 @@ describe('ReactStrictMode', () => {
819 }).toWarnDev(
820 'Warning: A string ref, "somestring", has been found within a strict mode tree. ' +
821 'String refs are a source of potential bugs and should be avoided. ' +
822 - 'We recommend using a ref callback instead.\n\n' +
822 + 'We recommend using createRef() instead.\n\n' +
823 ' in InnerComponent (at **)\n' +
824 ' in OuterComponent (at **)\n\n' +
825 'Learn more about using refs safely here:\n' +
packages/shared/ReactTypes.js
+4
@@ -97,3 +97,7 @@ export type ReactPortal = {
97 // TODO: figure out the API for cross-renderer implementation.
98 implementation: any,
99 };
100 +
101 +export type RefObject = {|
102 + value: any,
103 +|};