Skip to content

Commit 20e5daf

Browse files
committed
Pass ref as normal prop
1 parent 1b9c328 commit 20e5daf

22 files changed

+350
-128
lines changed

packages/react-client/src/ReactFlightClient.js

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,11 @@ import type {
3535

3636
import type {Postpone} from 'react/src/ReactPostpone';
3737

38-
import {enableBinaryFlight, enablePostpone} from 'shared/ReactFeatureFlags';
38+
import {
39+
enableBinaryFlight,
40+
enablePostpone,
41+
enableFastJSX,
42+
} from 'shared/ReactFeatureFlags';
3943

4044
import {
4145
resolveClientReference,
@@ -468,19 +472,34 @@ function createElement(
468472
key: mixed,
469473
props: mixed,
470474
): React$Element<any> {
471-
const element: any = {
472-
// This tag allows us to uniquely identify this as a React Element
473-
$$typeof: REACT_ELEMENT_TYPE,
474-
475-
// Built-in properties that belong on the element
476-
type: type,
477-
key: key,
478-
ref: null,
479-
props: props,
480-
481-
// Record the component responsible for creating this element.
482-
_owner: null,
483-
};
475+
const element: any = enableFastJSX
476+
? {
477+
// TODO: Remove this field
478+
ref: null,
479+
480+
// This tag allows us to uniquely identify this as a React Element
481+
$$typeof: REACT_ELEMENT_TYPE,
482+
483+
// Built-in properties that belong on the element
484+
type,
485+
key,
486+
props,
487+
488+
// Record the component responsible for creating this element.
489+
_owner: null,
490+
}
491+
: {
492+
// Old behavior. When enableFastJSX is off, `ref` is an extra field.
493+
ref: null,
494+
495+
// Everything else is the same.
496+
$$typeof: REACT_ELEMENT_TYPE,
497+
type,
498+
key,
499+
props,
500+
_owner: null,
501+
};
502+
484503
if (__DEV__) {
485504
// We don't really need to add any of these but keeping them for good measure.
486505
// Unfortunately, _store is enumerable in jest matchers so for equality to

packages/react-dom-bindings/src/client/ReactDOMComponent.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -640,7 +640,9 @@ function setProp(
640640
case 'suppressHydrationWarning':
641641
case 'defaultValue': // Reserved
642642
case 'defaultChecked':
643-
case 'innerHTML': {
643+
case 'innerHTML':
644+
case 'ref': {
645+
// TODO: `ref` is pretty common, should we move it up?
644646
// Noop
645647
break;
646648
}
@@ -988,7 +990,8 @@ function setPropOnCustomElement(
988990
}
989991
case 'suppressContentEditableWarning':
990992
case 'suppressHydrationWarning':
991-
case 'innerHTML': {
993+
case 'innerHTML':
994+
case 'ref': {
992995
// Noop
993996
break;
994997
}
@@ -2194,6 +2197,7 @@ function diffHydratedCustomComponent(
21942197
case 'defaultValue':
21952198
case 'defaultChecked':
21962199
case 'innerHTML':
2200+
case 'ref':
21972201
// Noop
21982202
continue;
21992203
case 'dangerouslySetInnerHTML':
@@ -2307,6 +2311,7 @@ function diffHydratedGenericElement(
23072311
case 'defaultValue':
23082312
case 'defaultChecked':
23092313
case 'innerHTML':
2314+
case 'ref':
23102315
// Noop
23112316
continue;
23122317
case 'dangerouslySetInnerHTML':

packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1226,6 +1226,7 @@ function pushAttribute(
12261226
case 'innerHTML': // Must use dangerouslySetInnerHTML instead.
12271227
case 'suppressContentEditableWarning':
12281228
case 'suppressHydrationWarning':
1229+
case 'ref':
12291230
// Ignored. These are built-in to React on the client.
12301231
return;
12311232
case 'autoFocus':
@@ -3391,6 +3392,7 @@ function pushStartCustomElement(
33913392
break;
33923393
case 'suppressContentEditableWarning':
33933394
case 'suppressHydrationWarning':
3395+
case 'ref':
33943396
// Ignored. These are built-in to React on the client.
33953397
break;
33963398
case 'className':
@@ -4964,6 +4966,7 @@ function writeStyleResourceAttributeInJS(
49644966
case 'suppressContentEditableWarning':
49654967
case 'suppressHydrationWarning':
49664968
case 'style':
4969+
case 'ref':
49674970
// Ignored
49684971
return;
49694972

@@ -5157,6 +5160,7 @@ function writeStyleResourceAttributeInAttr(
51575160
case 'suppressContentEditableWarning':
51585161
case 'suppressHydrationWarning':
51595162
case 'style':
5163+
case 'ref':
51605164
// Ignored
51615165
return;
51625166

packages/react-dom-bindings/src/shared/ReactDOMUnknownPropertyHook.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,8 @@ function validateProperty(tagName, name, value, eventRegistry) {
186186
case 'suppressHydrationWarning':
187187
case 'defaultValue': // Reserved
188188
case 'defaultChecked':
189-
case 'innerHTML': {
189+
case 'innerHTML':
190+
case 'ref': {
190191
return true;
191192
}
192193
case 'innerText': // Properties

packages/react-dom/src/__tests__/ReactCompositeComponent-test.js

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -278,26 +278,48 @@ describe('ReactCompositeComponent', () => {
278278
}
279279
}
280280

281+
function refFn1(ref) {
282+
instance1 = ref;
283+
}
284+
285+
function refFn2(ref) {
286+
instance2 = ref;
287+
}
288+
289+
function refFn3(ref) {
290+
instance3 = ref;
291+
}
292+
281293
let instance1;
282294
let instance2;
283295
let instance3;
284296
const root = ReactDOMClient.createRoot(document.createElement('div'));
285297
await act(() => {
286-
root.render(<Component ref={ref => (instance1 = ref)} />);
298+
root.render(<Component ref={refFn1} />);
287299
});
288-
expect(instance1.props).toEqual({prop: 'testKey'});
300+
if (gate(flags => flags.enableFastJSX)) {
301+
expect(instance1.props).toEqual({prop: 'testKey', ref: refFn1});
302+
} else {
303+
expect(instance1.props).toEqual({prop: 'testKey'});
304+
}
289305

290306
await act(() => {
291-
root.render(
292-
<Component ref={ref => (instance2 = ref)} prop={undefined} />,
293-
);
307+
root.render(<Component ref={refFn2} prop={undefined} />);
294308
});
295-
expect(instance2.props).toEqual({prop: 'testKey'});
309+
if (gate(flags => flags.enableFastJSX)) {
310+
expect(instance2.props).toEqual({prop: 'testKey', ref: refFn2});
311+
} else {
312+
expect(instance2.props).toEqual({prop: 'testKey'});
313+
}
296314

297315
await act(() => {
298-
root.render(<Component ref={ref => (instance3 = ref)} prop={null} />);
316+
root.render(<Component ref={refFn3} prop={null} />);
299317
});
300-
expect(instance3.props).toEqual({prop: null});
318+
if (gate(flags => flags.enableFastJSX)) {
319+
expect(instance3.props).toEqual({prop: null, ref: refFn3});
320+
} else {
321+
expect(instance3.props).toEqual({prop: null});
322+
}
301323
});
302324

303325
it('should not mutate passed-in props object', async () => {

packages/react-dom/src/__tests__/ReactDeprecationWarnings-test.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ describe('ReactDeprecationWarnings', () => {
109109
await waitForAll([]);
110110
});
111111

112+
// @gate !enableFastJSX
112113
it('should warn when owner and self are different for string refs', async () => {
113114
class RefComponent extends React.Component {
114115
render() {
@@ -140,6 +141,7 @@ describe('ReactDeprecationWarnings', () => {
140141
});
141142

142143
if (__DEV__) {
144+
// @gate !enableFastJSX
143145
it('should warn when owner and self are different for string refs', async () => {
144146
class RefComponent extends React.Component {
145147
render() {

packages/react-dom/src/__tests__/ReactFunctionComponent-test.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ describe('ReactFunctionComponent', () => {
193193
);
194194
});
195195

196+
// @gate !enableFastJSX
196197
it('should warn when given a string ref', () => {
197198
function Indirection(props) {
198199
return <div>{props.children}</div>;
@@ -226,6 +227,7 @@ describe('ReactFunctionComponent', () => {
226227
ReactTestUtils.renderIntoDocument(<ParentUsingStringRef />);
227228
});
228229

230+
// @gate !enableFastJSX
229231
it('should warn when given a function ref', () => {
230232
function Indirection(props) {
231233
return <div>{props.children}</div>;
@@ -264,6 +266,7 @@ describe('ReactFunctionComponent', () => {
264266
ReactTestUtils.renderIntoDocument(<ParentUsingFunctionRef />);
265267
});
266268

269+
// @gate !enableFastJSX
267270
it('deduplicates ref warnings based on element or owner', () => {
268271
// When owner uses JSX, we can use exact line location to dedupe warnings
269272
class AnonymousParentUsingJSX extends React.Component {
@@ -327,6 +330,7 @@ describe('ReactFunctionComponent', () => {
327330
// This guards against a regression caused by clearing the current debug fiber.
328331
// https://github.com/facebook/react/issues/10831
329332
// @gate !disableLegacyContext || !__DEV__
333+
// @gate !enableFastJSX
330334
it('should warn when giving a function ref with context', () => {
331335
function Child() {
332336
return null;

packages/react-dom/src/__tests__/refs-test.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,6 @@ describe('ref swapping', () => {
418418
type: 'div',
419419
props: {},
420420
key: null,
421-
ref: null,
422421
});
423422
});
424423

@@ -428,9 +427,10 @@ describe('ref swapping', () => {
428427
root.render({
429428
$$typeof: Symbol.for('react.element'),
430429
type: 'div',
431-
props: {},
430+
props: {
431+
ref: NaN,
432+
},
432433
key: null,
433-
ref: undefined,
434434
});
435435
});
436436
}).rejects.toThrow(

packages/react-reconciler/src/ReactChildFiber.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import {
3636
import {ClassComponent, HostText, HostPortal, Fragment} from './ReactWorkTags';
3737
import isArray from 'shared/isArray';
3838
import {checkPropStringCoercion} from 'shared/CheckStringCoercion';
39+
import {enableFastJSX} from 'shared/ReactFeatureFlags';
3940

4041
import {
4142
createWorkInProgress,
@@ -145,7 +146,19 @@ function coerceRef(
145146
current: Fiber | null,
146147
element: ReactElement,
147148
) {
148-
const mixedRef = element.ref;
149+
let mixedRef;
150+
if (enableFastJSX) {
151+
// TODO: This is a temporary, intermediate step. When enableFastJSX is on,
152+
// we should resolve the `ref` prop during the begin phase of the component
153+
// it's attached to (HostComponent, ClassComponent, etc).
154+
155+
const refProp = element.props.ref;
156+
mixedRef = refProp !== undefined ? refProp : null;
157+
} else {
158+
// Old behavior.
159+
mixedRef = element.ref;
160+
}
161+
149162
if (
150163
mixedRef !== null &&
151164
typeof mixedRef !== 'function' &&

packages/react-reconciler/src/ReactFiberBeginWork.js

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ import {
111111
enableAsyncActions,
112112
enablePostpone,
113113
enableRenderableContext,
114+
enableFastJSX,
114115
} from 'shared/ReactFeatureFlags';
115116
import isArray from 'shared/isArray';
116117
import shallowEqual from 'shared/shallowEqual';
@@ -421,6 +422,24 @@ function updateForwardRef(
421422
const render = Component.render;
422423
const ref = workInProgress.ref;
423424

425+
let propsWithoutRef;
426+
if (enableFastJSX && 'ref' in nextProps) {
427+
// `ref` is just a prop now, but `forwardRef` expects it to not appear in
428+
// the props object. This used to happen in the JSX runtime, but now we do
429+
// it here.
430+
propsWithoutRef = {};
431+
for (const key in nextProps) {
432+
// Since `ref` should only appear in props via the JSX transform, we can
433+
// assume that this is a plain object. So we don't need a
434+
// hasOwnProperty check.
435+
if (key !== 'ref') {
436+
propsWithoutRef[key] = nextProps[key];
437+
}
438+
}
439+
} else {
440+
propsWithoutRef = nextProps;
441+
}
442+
424443
// The rest is a fork of updateFunctionComponent
425444
let nextChildren;
426445
let hasId;
@@ -435,7 +454,7 @@ function updateForwardRef(
435454
current,
436455
workInProgress,
437456
render,
438-
nextProps,
457+
propsWithoutRef,
439458
ref,
440459
renderLanes,
441460
);
@@ -446,7 +465,7 @@ function updateForwardRef(
446465
current,
447466
workInProgress,
448467
render,
449-
nextProps,
468+
propsWithoutRef,
450469
ref,
451470
renderLanes,
452471
);
@@ -2095,7 +2114,7 @@ function validateFunctionComponentInDev(workInProgress: Fiber, Component: any) {
20952114
);
20962115
}
20972116
}
2098-
if (workInProgress.ref !== null) {
2117+
if (!enableFastJSX && workInProgress.ref !== null) {
20992118
let info = '';
21002119
const componentName = getComponentNameFromType(Component) || 'Unknown';
21012120
const ownerName = getCurrentFiberOwnerNameInDevOrNull();

0 commit comments

Comments
 (0)