21
import isArray from 'shared/isArray';
22
import {describeUnknownElementTypeFrameInDEV} from 'shared/ReactComponentStackFrame';
23
import checkPropTypes from 'shared/checkPropTypes';
24
+import {enableRefAsProp} from 'shared/ReactFeatureFlags';
25
26
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
27
const ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;
31
let specialPropKeyWarningShown;
32
let specialPropRefWarningShown;
33
let didWarnAboutStringRefs;
34
+let didWarnAboutElementRef;
35
36
if (__DEV__) {
37
didWarnAboutStringRefs = {};
38
+ didWarnAboutElementRef = {};
39
}
40
41
function hasValidRef(config) {
114
}
115
116
function defineRefPropWarningGetter(props, displayName) {
117
+ if (!enableRefAsProp) {
118
+ if (__DEV__) {
119
+ const warnAboutAccessingRef = function () {
120
+ if (!specialPropRefWarningShown) {
121
+ specialPropRefWarningShown = true;
122
+ console.error(
123
+ '%s: `ref` is not a prop. Trying to access it will result ' +
124
+ 'in `undefined` being returned. If you need to access the same ' +
125
+ 'value within the child component, you should pass it as a different ' +
126
+ 'prop. (https://reactjs.org/link/special-props)',
127
+ displayName,
128
+ );
129
+ }
130
+ };
131
+ warnAboutAccessingRef.isReactWarning = true;
132
+ Object.defineProperty(props, 'ref', {
133
+ get: warnAboutAccessingRef,
134
+ configurable: true,
135
+ });
136
+ }
137
+ }
138
+}
139
+
140
+function elementRefGetterWithDeprecationWarning() {
141
if (__DEV__) {
115
- const warnAboutAccessingRef = function () {
116
- if (!specialPropRefWarningShown) {
117
- specialPropRefWarningShown = true;
118
- console.error(
119
- '%s: `ref` is not a prop. Trying to access it will result ' +
120
- 'in `undefined` being returned. If you need to access the same ' +
121
- 'value within the child component, you should pass it as a different ' +
122
- 'prop. (https://reactjs.org/link/special-props)',
123
- displayName,
124
- );
125
- }
126
- };
127
- warnAboutAccessingRef.isReactWarning = true;
128
- Object.defineProperty(props, 'ref', {
129
- get: warnAboutAccessingRef,
130
- configurable: true,
131
- });
142
+ const componentName = getComponentNameFromType(this.type);
143
+ if (!didWarnAboutElementRef[componentName]) {
144
+ didWarnAboutElementRef[componentName] = true;
145
+ console.error(
146
+ 'Accessing element.ref is no longer supported. ref is now a ' +
147
+ 'regular prop. It will be removed from the JSX Element ' +
148
+ 'type in a future release.',
149
+ );
150
+ }
151
+
152
+ // An undefined `element.ref` is coerced to `null` for
153
+ // backwards compatibility.
154
+ const refProp = this.props.ref;
155
+ return refProp !== undefined ? refProp : null;
156
}
157
}
158
176
* indicating filename, line number, and/or other information.
177
* @internal
178
*/
155
-function ReactElement(type, key, ref, self, source, owner, props) {
156
- const element = {
157
- // This tag allows us to uniquely identify this as a React Element
158
- $$typeof: REACT_ELEMENT_TYPE,
179
+function ReactElement(type, key, _ref, self, source, owner, props) {
180
+ let ref;
181
+ if (enableRefAsProp) {
182
+ // When enableRefAsProp is on, ignore whatever was passed as the ref
183
+ // argument and treat `props.ref` as the source of truth. The only thing we
184
+ // use this for is `element.ref`, which will log a deprecation warning on
185
+ // access. In the next release, we can remove `element.ref` as well as the
186
+ // `ref` argument.
187
+ const refProp = props.ref;
188
+
189
+ // An undefined `element.ref` is coerced to `null` for
190
+ // backwards compatibility.
191
+ ref = refProp !== undefined ? refProp : null;
192
+ } else {
193
+ ref = _ref;
194
+ }
195
160
- // Built-in properties that belong on the element
161
- type,
162
- key,
163
- ref,
164
- props,
196
+ let element;
197
+ if (__DEV__ && enableRefAsProp) {
198
+ // In dev, make `ref` a non-enumerable property with a warning. It's non-
199
+ // enumerable so that test matchers and serializers don't access it and
200
+ // trigger the warning.
201
+ //
202
+ // `ref` will be removed from the element completely in a future release.
203
+ element = {
204
+ // This tag allows us to uniquely identify this as a React Element
205
+ $$typeof: REACT_ELEMENT_TYPE,
206
+
207
+ // Built-in properties that belong on the element
208
+ type,
209
+ key,
210
166
- // Record the component responsible for creating this element.
167
- _owner: owner,
168
- };
211
+ props,
212
+
213
+ // Record the component responsible for creating this element.
214
+ _owner: owner,
215
+ };
216
+ if (ref !== null) {
217
+ Object.defineProperty(element, 'ref', {
218
+ enumerable: false,
219
+ get: elementRefGetterWithDeprecationWarning,
220
+ });
221
+ } else {
222
+ // Don't warn on access if a ref is not given. This reduces false
223
+ // positives in cases where a test serializer uses
224
+ // getOwnPropertyDescriptors to compare objects, like Jest does, which is
225
+ // a problem because it bypasses non-enumerability.
226
+ //
227
+ // So unfortunately this will trigger a false positive warning in Jest
228
+ // when the diff is printed:
229
+ //
230
+ // expect(<div ref={ref} />).toEqual(<span ref={ref} />);
231
+ //
232
+ // A bit sketchy, but this is what we've done for the `props.key` and
233
+ // `props.ref` accessors for years, which implies it will be good enough
234
+ // for `element.ref`, too. Let's see if anyone complains.
235
+ Object.defineProperty(element, 'ref', {
236
+ enumerable: false,
237
+ value: null,
238
+ });
239
+ }
240
+ } else {
241
+ // In prod, `ref` is a regular property. It will be removed in a
242
+ // future release.
243
+ element = {
244
+ // This tag allows us to uniquely identify this as a React Element
245
+ $$typeof: REACT_ELEMENT_TYPE,
246
+
247
+ // Built-in properties that belong on the element
248
+ type,
249
+ key,
250
+ ref,
251
+
252
+ props,
253
+
254
+ // Record the component responsible for creating this element.
255
+ _owner: owner,
256
+ };
257
+ }
258
259
if (__DEV__) {
260
// The validation flag is currently mutative. We put it on
325
}
326
327
if (hasValidRef(config)) {
239
- ref = config.ref;
328
+ if (!enableRefAsProp) {
329
+ ref = config.ref;
330
+ }
331
}
332
333
// Remaining properties are added to a new props object
336
hasOwnProperty.call(config, propName) &&
337
// Skip over reserved prop names
338
propName !== 'key' &&
248
- // TODO: `ref` will no longer be reserved in the next major
249
- propName !== 'ref'
339
+ (enableRefAsProp || propName !== 'ref')
340
) {
341
props[propName] = config[propName];
342
}
543
}
544
545
if (hasValidRef(config)) {
456
- ref = config.ref;
546
+ if (!enableRefAsProp) {
547
+ ref = config.ref;
548
+ }
549
warnIfStringRefCannotBeAutoConverted(config, self);
550
}
551
555
hasOwnProperty.call(config, propName) &&
556
// Skip over reserved prop names
557
propName !== 'key' &&
466
- // TODO: `ref` will no longer be reserved in the next major
467
- propName !== 'ref'
558
+ (enableRefAsProp || propName !== 'ref')
559
) {
560
props[propName] = config[propName];
561
}
571
}
572
}
573
483
- if (key || ref) {
574
+ if (key || (!enableRefAsProp && ref)) {
575
const displayName =
576
typeof type === 'function'
577
? type.displayName || type.name || 'Unknown'
579
if (key) {
580
defineKeyPropWarningGetter(props, displayName);
581
}
491
- if (ref) {
582
+ if (!enableRefAsProp && ref) {
583
defineRefPropWarningGetter(props, displayName);
584
}
585
}
680
681
if (config != null) {
682
if (hasValidRef(config)) {
592
- ref = config.ref;
683
+ if (!enableRefAsProp) {
684
+ ref = config.ref;
685
+ }
686
687
if (__DEV__) {
688
warnIfStringRefCannotBeAutoConverted(config, config.__self);
701
hasOwnProperty.call(config, propName) &&
702
// Skip over reserved prop names
703
propName !== 'key' &&
611
- // TODO: `ref` will no longer be reserved in the next major
612
- propName !== 'ref' &&
613
- // ...and maybe these, too, though we currently rely on them for
614
- // warnings and debug information in dev. Need to decide if we're OK
615
- // with dropping them. In the jsx() runtime it's not an issue because
616
- // the data gets passed as separate arguments instead of props, but
617
- // it would be nice to stop relying on them entirely so we can drop
618
- // them from the internal Fiber field.
704
+ (enableRefAsProp || propName !== 'ref') &&
705
+ // Even though we don't use these anymore in the runtime, we don't want
706
+ // them to appear as props, so in createElement we filter them out.
707
+ // We don't have to do this in the jsx() runtime because the jsx()
708
+ // transform never passed these as props; it used separate arguments.
709
propName !== '__self' &&
710
propName !== '__source'
711
) {
742
}
743
}
744
if (__DEV__) {
655
- if (key || ref) {
745
+ if (key || (!enableRefAsProp && ref)) {
746
const displayName =
747
typeof type === 'function'
748
? type.displayName || type.name || 'Unknown'
750
if (key) {
751
defineKeyPropWarningGetter(props, displayName);
752
}
663
- if (ref) {
753
+ if (!enableRefAsProp && ref) {
754
defineRefPropWarningGetter(props, displayName);
755
}
756
}
822
return ReactElement(
823
oldElement.type,
824
newKey,
735
- oldElement.ref,
825
+ // When enableRefAsProp is on, this argument is ignored. This check only
826
+ // exists to avoid the `ref` access warning.
827
+ enableRefAsProp ? null : oldElement.ref,
828
undefined,
829
undefined,
830
oldElement._owner,
850
851
// Reserved names are extracted
852
let key = element.key;
761
- let ref = element.ref;
853
+ let ref = enableRefAsProp ? null : element.ref;
854
855
// Owner will be preserved, unless ref is overridden
856
let owner = element._owner;
857
858
if (config != null) {
859
if (hasValidRef(config)) {
768
- // Silently steal the ref from the parent.
769
- ref = config.ref;
860
+ if (!enableRefAsProp) {
861
+ // Silently steal the ref from the parent.
862
+ ref = config.ref;
863
+ }
864
owner = ReactCurrentOwner.current;
865
}
866
if (hasValidKey(config)) {
880
hasOwnProperty.call(config, propName) &&
881
// Skip over reserved prop names
882
propName !== 'key' &&
789
- // TODO: `ref` will no longer be reserved in the next major
790
- propName !== 'ref' &&
883
+ (enableRefAsProp || propName !== 'ref') &&
884
// ...and maybe these, too, though we currently rely on them for
885
// warnings and debug information in dev. Need to decide if we're OK
886
// with dropping them. In the jsx() runtime it's not an issue because
888
// it would be nice to stop relying on them entirely so we can drop
889
// them from the internal Fiber field.
890
propName !== '__self' &&
798
- propName !== '__source'
891
+ propName !== '__source' &&
892
+ // Undefined `ref` is ignored by cloneElement. We treat it the same as
893
+ // if the property were missing. This is mostly for
894
+ // backwards compatibility.
895
+ !(enableRefAsProp && propName === 'ref' && config.ref === undefined)
896
) {
897
if (config[propName] === undefined && defaultProps !== undefined) {
898
// Resolve default props
1113
* @param {ReactElement} fragment
1114
*/
1115
function validateFragmentProps(fragment) {
1116
+ // TODO: Move this to render phase instead of at element creation.
1117
if (__DEV__) {
1118
const keys = Object.keys(fragment.props);
1119
for (let i = 0; i < keys.length; i++) {
1130
}
1131
}
1132
1035
- if (fragment.ref !== null) {
1133
+ if (!enableRefAsProp && fragment.ref !== null) {
1134
setCurrentlyValidatingElement(fragment);
1135
console.error('Invalid attribute `ref` supplied to `React.Fragment`.');
1136
setCurrentlyValidatingElement(null);