[string-refs] log string ref from prod (#31161)
If passed as a feature flag, this calls the configured function when a string ref is used even from prod code to find the last usages.
Jan Kassens committed
Oct 10, 2024 at 18:12 UTC
5636fad840942cfea80301d91e931a50c6370d19
9 files changed
+55
-20
packages/react/src/jsx/ReactJSXElement.js
+17
-11
@@ -24,6 +24,7 @@ import {
24
disableStringRefs,
25
disableDefaultPropsExceptForClasses,
26
enableOwnerStacks,
27
+ enableLogStringRefsProd,
28
} from 'shared/ReactFeatureFlags';
29
import {checkPropStringCoercion} from 'shared/CheckStringCoercion';
30
import {ClassComponent} from 'react-reconciler/src/ReactWorkTags';
@@ -76,7 +77,7 @@ let didWarnAboutStringRefs;
77
let didWarnAboutElementRef;
78
let didWarnAboutOldJSXRuntime;
79
79
-if (__DEV__) {
80
+if (__DEV__ || enableLogStringRefsProd) {
81
didWarnAboutStringRefs = {};
82
didWarnAboutElementRef = {};
83
}
@@ -1314,22 +1315,27 @@ function stringRefAsCallbackRef(stringRef, type, owner, value) {
1315
);
1316
}
1317
1317
- if (__DEV__) {
1318
+ if (__DEV__ || enableLogStringRefsProd) {
1319
if (
1320
// Will already warn with "Function components cannot be given refs"
1321
!(typeof type === 'function' && !isReactClass(type))
1322
) {
1323
const componentName = getComponentNameFromFiber(owner) || 'Component';
1324
if (!didWarnAboutStringRefs[componentName]) {
1324
- console.error(
1325
- 'Component "%s" contains the string ref "%s". Support for string refs ' +
1326
- 'will be removed in a future major release. We recommend using ' +
1327
- 'useRef() or createRef() instead. ' +
1328
- 'Learn more about using refs safely here: ' +
1329
- 'https://react.dev/link/strict-mode-string-ref',
1330
- componentName,
1331
- stringRef,
1332
- );
1325
+ if (enableLogStringRefsProd) {
1326
+ enableLogStringRefsProd(componentName, stringRef);
1327
+ }
1328
+ if (__DEV__) {
1329
+ console.error(
1330
+ 'Component "%s" contains the string ref "%s". Support for string refs ' +
1331
+ 'will be removed in a future major release. We recommend using ' +
1332
+ 'useRef() or createRef() instead. ' +
1333
+ 'Learn more about using refs safely here: ' +
1334
+ 'https://react.dev/link/strict-mode-string-ref',
1335
+ componentName,
1336
+ stringRef,
1337
+ );
1338
+ }
1339
didWarnAboutStringRefs[componentName] = true;
1340
}
1341
}
packages/shared/ReactFeatureFlags.js
+7
@@ -215,6 +215,13 @@ export const disableClientCache = true;
215
// during element creation.
216
export const enableRefAsProp = true;
217
export const disableStringRefs = true;
218
+/**
219
+ * If set to a function, the function will be called with the component name
220
+ * and ref string.
221
+ *
222
+ * NOTE: This happens also in the production build.
223
+ */
224
+export const enableLogStringRefsProd: null | ((string, string) => void) = null;
225
226
// Warn on any usage of ReactTestRenderer
227
export const enableReactTestRendererWarning = true;
packages/shared/forks/ReactFeatureFlags.native-fb.js
+1
@@ -65,6 +65,7 @@ export const enableLazyContextPropagation = true;
65
export const enableLegacyCache = false;
66
export const enableLegacyFBSupport = false;
67
export const enableLegacyHidden = false;
68
+export const enableLogStringRefsProd: null | ((string, string) => void) = null;
69
export const enableNoCloningMemoCache = false;
70
export const enableOwnerStacks = false;
71
export const enablePostpone = false;
packages/shared/forks/ReactFeatureFlags.native-oss.js
+1
@@ -56,6 +56,7 @@ export const enableContextProfiling = false;
56
export const enableLegacyCache = false;
57
export const enableLegacyFBSupport = false;
58
export const enableLegacyHidden = false;
59
+export const enableLogStringRefsProd: null | ((string, string) => void) = null;
60
export const enableNoCloningMemoCache = false;
61
export const enableObjectFiber = false;
62
export const enableOwnerStacks = false;
packages/shared/forks/ReactFeatureFlags.test-renderer.js
+1
@@ -44,6 +44,7 @@ export const enableUseEffectEventHook = false;
44
export const favorSafetyOverHydrationPerf = true;
45
export const enableComponentStackLocations = true;
46
export const enableLegacyFBSupport = false;
47
+export const enableLogStringRefsProd: null | ((string, string) => void) = null;
48
export const enableFilterEmptyStringAttributesDOM = true;
49
export const enableGetInspectorDataForInstanceInProduction = false;
50
export const enableFabricCompleteRootInCommitPhase = false;
packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+1
@@ -46,6 +46,7 @@ export const enableUseEffectEventHook = false;
46
export const favorSafetyOverHydrationPerf = true;
47
export const enableComponentStackLocations = true;
48
export const enableLegacyFBSupport = false;
49
+export const enableLogStringRefsProd: null | ((string, string) => void) = null;
50
export const enableFilterEmptyStringAttributesDOM = true;
51
export const enableGetInspectorDataForInstanceInProduction = false;
52
export const enableRenderableContext = false;
packages/shared/forks/ReactFeatureFlags.www-dynamic.js
+1
@@ -21,6 +21,7 @@ export const disableSchedulerTimeoutInWorkLoop = __VARIANT__;
21
export const enableDeferRootSchedulingToMicrotask = __VARIANT__;
22
export const enableDO_NOT_USE_disableStrictPassiveEffect = __VARIANT__;
23
export const enableHiddenSubtreeInsertionEffectCleanup = __VARIANT__;
24
+export const enableLogStringRefsProd: null | ((string, string) => void) = null;
25
export const enableNoCloningMemoCache = __VARIANT__;
26
export const enableObjectFiber = __VARIANT__;
27
export const enableRenderableContext = __VARIANT__;
packages/shared/forks/ReactFeatureFlags.www.js
+3
-2
@@ -22,20 +22,21 @@ export const {
22
enableDebugTracing,
23
enableDeferRootSchedulingToMicrotask,
24
enableDO_NOT_USE_disableStrictPassiveEffect,
25
+ enableHiddenSubtreeInsertionEffectCleanup,
26
enableInfiniteRenderLoopDetection,
27
+ enableLogStringRefsProd,
28
enableNoCloningMemoCache,
29
enableObjectFiber,
30
enableRenderableContext,
31
enableRetryLaneExpiration,
32
+ enableSiblingPrerendering,
33
enableTransitionTracing,
34
enableTrustedTypesIntegration,
32
- enableHiddenSubtreeInsertionEffectCleanup,
35
favorSafetyOverHydrationPerf,
36
renameElementSymbol,
37
retryLaneExpirationMs,
38
syncLaneExpirationMs,
39
transitionLaneExpirationMs,
38
- enableSiblingPrerendering,
40
} = dynamicFeatureFlags;
41
42
// On WWW, __EXPERIMENTAL__ is used for a new modern build.
scripts/flags/flags.js
+23
-7
@@ -172,7 +172,7 @@ function getNextMajorFlagValue(flag) {
172
const value = ReactFeatureFlagsMajor[flag];
173
if (value === true || value === 'next') {
174
return '✅';
175
- } else if (value === false || value === 'experimental') {
175
+ } else if (value === false || value === null || value === 'experimental') {
176
return '❌';
177
} else if (value === 'profile') {
178
return '📊';
@@ -189,7 +189,12 @@ function getOSSCanaryFlagValue(flag) {
189
const value = ReactFeatureFlags[flag];
190
if (value === true) {
191
return '✅';
192
- } else if (value === false || value === 'experimental' || value === 'next') {
192
+ } else if (
193
+ value === false ||
194
+ value === null ||
195
+ value === 'experimental' ||
196
+ value === 'next'
197
+ ) {
198
return '❌';
199
} else if (value === 'profile') {
200
return '📊';
@@ -206,7 +211,7 @@ function getOSSExperimentalFlagValue(flag) {
211
const value = ReactFeatureFlags[flag];
212
if (value === true || value === 'experimental') {
213
return '✅';
209
- } else if (value === false || value === 'next') {
214
+ } else if (value === false || value === null || value === 'next') {
215
return '❌';
216
} else if (value === 'profile') {
217
return '📊';
@@ -225,7 +230,7 @@ function getWWWModernFlagValue(flag) {
230
const value = ReactFeatureFlagsWWW[flag];
231
if (value === true || value === 'experimental') {
232
return '✅';
228
- } else if (value === false || value === 'next') {
233
+ } else if (value === false || value === null || value === 'next') {
234
return '❌';
235
} else if (value === 'profile') {
236
return '📊';
@@ -244,7 +249,12 @@ function getWWWClassicFlagValue(flag) {
249
const value = ReactFeatureFlagsWWW[flag];
250
if (value === true) {
251
return '✅';
247
- } else if (value === false || value === 'experimental' || value === 'next') {
252
+ } else if (
253
+ value === false ||
254
+ value === null ||
255
+ value === 'experimental' ||
256
+ value === 'next'
257
+ ) {
258
return '❌';
259
} else if (value === 'profile') {
260
return '📊';
@@ -265,7 +275,7 @@ function getRNNextMajorFlagValue(flag) {
275
return '✅';
276
} else if (value === 'next-todo') {
277
return '📋';
268
- } else if (value === false || value === 'experimental') {
278
+ } else if (value === false || value === null || value === 'experimental') {
279
return '❌';
280
} else if (value === 'profile') {
281
return '📊';
@@ -286,6 +296,7 @@ function getRNOSSFlagValue(flag) {
296
return '✅';
297
} else if (
298
value === false ||
299
+ value === null ||
300
value === 'experimental' ||
301
value === 'next' ||
302
value === 'next-todo'
@@ -308,7 +319,12 @@ function getRNFBFlagValue(flag) {
319
const value = ReactFeatureFlagsNativeFB[flag];
320
if (value === true) {
321
return '✅';
311
- } else if (value === false || value === 'experimental' || value === 'next') {
322
+ } else if (
323
+ value === false ||
324
+ value === null ||
325
+ value === 'experimental' ||
326
+ value === 'next'
327
+ ) {
328
return '❌';
329
} else if (value === 'profile') {
330
return '📊';