From e5f148dab87f08beb21967c25e8bd532d5345d83 Mon Sep 17 00:00:00 2001 From: Jan Kassens Date: Wed, 9 Oct 2024 16:38:14 -0400 Subject: [PATCH] [string-refs] log string ref from prod 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 | 1 + .../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 | 1 + scripts/flags/flags.js | 30 ++++++++++++++----- 9 files changed, 47 insertions(+), 18 deletions(-) diff --git a/packages/react/src/jsx/ReactJSXElement.js b/packages/react/src/jsx/ReactJSXElement.js index a0de9c8e145c8..4bd333262f2c1 100644 --- a/packages/react/src/jsx/ReactJSXElement.js +++ b/packages/react/src/jsx/ReactJSXElement.js @@ -24,6 +24,7 @@ import { disableStringRefs, disableDefaultPropsExceptForClasses, enableOwnerStacks, + logStringRefs, } 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__ || logStringRefs) { didWarnAboutStringRefs = {}; didWarnAboutElementRef = {}; } @@ -1314,22 +1315,27 @@ function stringRefAsCallbackRef(stringRef, type, owner, value) { ); } - if (__DEV__) { + if (__DEV__ || logStringRefs) { 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 (logStringRefs) { + logStringRefs(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 cd9bfedacabd5..02956543dc0ad 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -217,6 +217,7 @@ export const disableClientCache = true; // during element creation. export const enableRefAsProp = true; export const disableStringRefs = true; +export const logStringRefs: 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 9dcc0f5d033ed..5e5fe5c4473c2 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -90,6 +90,7 @@ export const enableUpdaterTracking = __PROFILE__; export const enableUseEffectEventHook = false; export const enableUseMemoCacheHook = true; export const favorSafetyOverHydrationPerf = true; +export const logStringRefs: null | ((string, string) => void) = null; export const renameElementSymbol = false; export const retryLaneExpirationMs = 5000; export const syncLaneExpirationMs = 250; diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index 741b44daf7926..4575d8bc4fd22 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -80,6 +80,7 @@ export const enableTrustedTypesIntegration = false; export const enableUseEffectEventHook = false; export const enableUseMemoCacheHook = true; export const favorSafetyOverHydrationPerf = true; +export const logStringRefs: null | ((string, string) => void) = null; export const passChildrenWhenCloningPersistedNodes = false; export const renameElementSymbol = true; export const retryLaneExpirationMs = 5000; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index 5e7d56a2a6f3e..23280d5dd70f5 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -48,6 +48,7 @@ export const enableFilterEmptyStringAttributesDOM = true; export const enableGetInspectorDataForInstanceInProduction = false; export const enableFabricCompleteRootInCommitPhase = false; export const enableHiddenSubtreeInsertionEffectCleanup = false; +export const logStringRefs: null | ((string, string) => void) = null; export const enableRetryLaneExpiration = false; export const retryLaneExpirationMs = 5000; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js index 59850bac75099..84afc0ac6a080 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js @@ -51,6 +51,7 @@ export const enableGetInspectorDataForInstanceInProduction = false; export const enableRenderableContext = false; export const enableFabricCompleteRootInCommitPhase = false; export const enableHiddenSubtreeInsertionEffectCleanup = true; +export const logStringRefs: null | ((string, string) => void) = null; export const enableRetryLaneExpiration = false; export const retryLaneExpirationMs = 5000; diff --git a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js index dbd00f671d84e..7565fa36556b6 100644 --- a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js +++ b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js @@ -32,6 +32,7 @@ export const renameElementSymbol = __VARIANT__; export const retryLaneExpirationMs = 5000; export const syncLaneExpirationMs = 250; export const transitionLaneExpirationMs = 5000; +export const logStringRefs: null | ((string, string) => void) = null; // Enable this flag to help with concurrent mode debugging. // It logs information to the console about React scheduling, rendering, and commit phases. diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js index 0caf25c155625..9c863f7e76ff3 100644 --- a/packages/shared/forks/ReactFeatureFlags.www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -37,6 +37,7 @@ export const { syncLaneExpirationMs, transitionLaneExpirationMs, enableSiblingPrerendering, + logStringRefs, } = 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 '📊';