From 5636fad840942cfea80301d91e931a50c6370d19 Mon Sep 17 00:00:00 2001 From: Jan Kassens Date: Thu, 10 Oct 2024 18:12:47 -0400 Subject: [PATCH] [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. --- packages/react/src/jsx/ReactJSXElement.js | 28 ++++++++++------- packages/shared/ReactFeatureFlags.js | 7 +++++ .../forks/ReactFeatureFlags.native-fb.js | 1 + .../forks/ReactFeatureFlags.native-oss.js | 1 + .../forks/ReactFeatureFlags.test-renderer.js | 1 + .../ReactFeatureFlags.test-renderer.www.js | 1 + .../forks/ReactFeatureFlags.www-dynamic.js | 1 + .../shared/forks/ReactFeatureFlags.www.js | 5 ++-- scripts/flags/flags.js | 30 ++++++++++++++----- 9 files changed, 55 insertions(+), 20 deletions(-) diff --git a/packages/react/src/jsx/ReactJSXElement.js b/packages/react/src/jsx/ReactJSXElement.js index a0de9c8e145c8..893806def2c47 100644 --- a/packages/react/src/jsx/ReactJSXElement.js +++ b/packages/react/src/jsx/ReactJSXElement.js @@ -24,6 +24,7 @@ import { disableStringRefs, disableDefaultPropsExceptForClasses, enableOwnerStacks, + enableLogStringRefsProd, } from 'shared/ReactFeatureFlags'; import {checkPropStringCoercion} from 'shared/CheckStringCoercion'; import {ClassComponent} from 'react-reconciler/src/ReactWorkTags'; @@ -76,7 +77,7 @@ let didWarnAboutStringRefs; let didWarnAboutElementRef; let didWarnAboutOldJSXRuntime; -if (__DEV__) { +if (__DEV__ || enableLogStringRefsProd) { didWarnAboutStringRefs = {}; didWarnAboutElementRef = {}; } @@ -1314,22 +1315,27 @@ function stringRefAsCallbackRef(stringRef, type, owner, value) { ); } - if (__DEV__) { + if (__DEV__ || enableLogStringRefsProd) { if ( // Will already warn with "Function components cannot be given refs" !(typeof type === 'function' && !isReactClass(type)) ) { const componentName = getComponentNameFromFiber(owner) || 'Component'; if (!didWarnAboutStringRefs[componentName]) { - console.error( - 'Component "%s" contains the string ref "%s". Support for string refs ' + - 'will be removed in a future major release. We recommend using ' + - 'useRef() or createRef() instead. ' + - 'Learn more about using refs safely here: ' + - 'https://react.dev/link/strict-mode-string-ref', - componentName, - stringRef, - ); + if (enableLogStringRefsProd) { + enableLogStringRefsProd(componentName, stringRef); + } + if (__DEV__) { + console.error( + 'Component "%s" contains the string ref "%s". Support for string refs ' + + 'will be removed in a future major release. We recommend using ' + + 'useRef() or createRef() instead. ' + + 'Learn more about using refs safely here: ' + + 'https://react.dev/link/strict-mode-string-ref', + componentName, + stringRef, + ); + } didWarnAboutStringRefs[componentName] = true; } } diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index 058e2be089b0f..5a11217452e57 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -215,6 +215,13 @@ export const disableClientCache = true; // during element creation. export const enableRefAsProp = true; export const disableStringRefs = true; +/** + * If set to a function, the function will be called with the component name + * and ref string. + * + * NOTE: This happens also in the production build. + */ +export const enableLogStringRefsProd: null | ((string, string) => void) = null; // Warn on any usage of ReactTestRenderer export const enableReactTestRendererWarning = true; diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index bac5977f5e1b4..de6ee64caad20 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -65,6 +65,7 @@ export const enableLazyContextPropagation = true; export const enableLegacyCache = false; export const enableLegacyFBSupport = false; export const enableLegacyHidden = false; +export const enableLogStringRefsProd: null | ((string, string) => void) = null; export const enableNoCloningMemoCache = false; export const enableOwnerStacks = false; export const enablePostpone = false; diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index d5f0d3bb9ca88..7b5e29a8fcc33 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -56,6 +56,7 @@ export const enableContextProfiling = false; export const enableLegacyCache = false; export const enableLegacyFBSupport = false; export const enableLegacyHidden = false; +export const enableLogStringRefsProd: null | ((string, string) => void) = null; export const enableNoCloningMemoCache = false; export const enableObjectFiber = false; export const enableOwnerStacks = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index 746778b7b2c27..2fe7b8a19a9d5 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -44,6 +44,7 @@ export const enableUseEffectEventHook = false; export const favorSafetyOverHydrationPerf = true; export const enableComponentStackLocations = true; export const enableLegacyFBSupport = false; +export const enableLogStringRefsProd: null | ((string, string) => void) = null; export const enableFilterEmptyStringAttributesDOM = true; export const enableGetInspectorDataForInstanceInProduction = false; export const enableFabricCompleteRootInCommitPhase = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js index 1fa1b40280ff3..7d9c3818c8f1b 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js @@ -46,6 +46,7 @@ export const enableUseEffectEventHook = false; export const favorSafetyOverHydrationPerf = true; export const enableComponentStackLocations = true; export const enableLegacyFBSupport = false; +export const enableLogStringRefsProd: null | ((string, string) => void) = null; export const enableFilterEmptyStringAttributesDOM = true; export const enableGetInspectorDataForInstanceInProduction = false; export const enableRenderableContext = false; diff --git a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js index 8a82b7f55241b..3888339bff2ed 100644 --- a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js +++ b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js @@ -21,6 +21,7 @@ export const disableSchedulerTimeoutInWorkLoop = __VARIANT__; export const enableDeferRootSchedulingToMicrotask = __VARIANT__; export const enableDO_NOT_USE_disableStrictPassiveEffect = __VARIANT__; export const enableHiddenSubtreeInsertionEffectCleanup = __VARIANT__; +export const enableLogStringRefsProd: null | ((string, string) => void) = null; export const enableNoCloningMemoCache = __VARIANT__; export const enableObjectFiber = __VARIANT__; export const enableRenderableContext = __VARIANT__; diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js index 95a5a4c55cacd..7484832a68b40 100644 --- a/packages/shared/forks/ReactFeatureFlags.www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -22,20 +22,21 @@ export const { enableDebugTracing, enableDeferRootSchedulingToMicrotask, enableDO_NOT_USE_disableStrictPassiveEffect, + enableHiddenSubtreeInsertionEffectCleanup, enableInfiniteRenderLoopDetection, + enableLogStringRefsProd, enableNoCloningMemoCache, enableObjectFiber, enableRenderableContext, enableRetryLaneExpiration, + enableSiblingPrerendering, enableTransitionTracing, enableTrustedTypesIntegration, - enableHiddenSubtreeInsertionEffectCleanup, favorSafetyOverHydrationPerf, renameElementSymbol, retryLaneExpirationMs, syncLaneExpirationMs, transitionLaneExpirationMs, - enableSiblingPrerendering, } = dynamicFeatureFlags; // On WWW, __EXPERIMENTAL__ is used for a new modern build. diff --git a/scripts/flags/flags.js b/scripts/flags/flags.js index 50d263cd498ef..a578304199a2c 100644 --- a/scripts/flags/flags.js +++ b/scripts/flags/flags.js @@ -172,7 +172,7 @@ function getNextMajorFlagValue(flag) { const value = ReactFeatureFlagsMajor[flag]; if (value === true || value === 'next') { return '✅'; - } else if (value === false || value === 'experimental') { + } else if (value === false || value === null || value === 'experimental') { return '❌'; } else if (value === 'profile') { return '📊'; @@ -189,7 +189,12 @@ function getOSSCanaryFlagValue(flag) { const value = ReactFeatureFlags[flag]; if (value === true) { return '✅'; - } else if (value === false || value === 'experimental' || value === 'next') { + } else if ( + value === false || + value === null || + value === 'experimental' || + value === 'next' + ) { return '❌'; } else if (value === 'profile') { return '📊'; @@ -206,7 +211,7 @@ function getOSSExperimentalFlagValue(flag) { const value = ReactFeatureFlags[flag]; if (value === true || value === 'experimental') { return '✅'; - } else if (value === false || value === 'next') { + } else if (value === false || value === null || value === 'next') { return '❌'; } else if (value === 'profile') { return '📊'; @@ -225,7 +230,7 @@ function getWWWModernFlagValue(flag) { const value = ReactFeatureFlagsWWW[flag]; if (value === true || value === 'experimental') { return '✅'; - } else if (value === false || value === 'next') { + } else if (value === false || value === null || value === 'next') { return '❌'; } else if (value === 'profile') { return '📊'; @@ -244,7 +249,12 @@ function getWWWClassicFlagValue(flag) { const value = ReactFeatureFlagsWWW[flag]; if (value === true) { return '✅'; - } else if (value === false || value === 'experimental' || value === 'next') { + } else if ( + value === false || + value === null || + value === 'experimental' || + value === 'next' + ) { return '❌'; } else if (value === 'profile') { return '📊'; @@ -265,7 +275,7 @@ function getRNNextMajorFlagValue(flag) { return '✅'; } else if (value === 'next-todo') { return '📋'; - } else if (value === false || value === 'experimental') { + } else if (value === false || value === null || value === 'experimental') { return '❌'; } else if (value === 'profile') { return '📊'; @@ -286,6 +296,7 @@ function getRNOSSFlagValue(flag) { return '✅'; } else if ( value === false || + value === null || value === 'experimental' || value === 'next' || value === 'next-todo' @@ -308,7 +319,12 @@ function getRNFBFlagValue(flag) { const value = ReactFeatureFlagsNativeFB[flag]; if (value === true) { return '✅'; - } else if (value === false || value === 'experimental' || value === 'next') { + } else if ( + value === false || + value === null || + value === 'experimental' || + value === 'next' + ) { return '❌'; } else if (value === 'profile') { return '📊';