diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts index 94be2100f57..dedfaad88af 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts @@ -621,38 +621,48 @@ function applySignature( const value = state.kind(effect.value); switch (value.kind) { case ValueKind.Frozen: { - const reason = getWriteErrorReason({ - kind: value.kind, - reason: value.reason, - }); - const variable = - effect.value.identifier.name !== null && - effect.value.identifier.name.kind === 'named' - ? `\`${effect.value.identifier.name.value}\`` - : 'value'; - const diagnostic = CompilerDiagnostic.create({ - category: ErrorCategory.Immutability, - reason: 'This value cannot be modified', - description: reason, - }).withDetails({ - kind: 'error', - loc: effect.value.loc, - message: `${variable} cannot be modified`, - }); - if ( + // Special case: assigning to ref.current is allowed even if ref is an argument + // This is the intended use of refs and should not be flagged as an error + const isLegitimateRefAssignment = effect.kind === 'Mutate' && - effect.reason?.kind === 'AssignCurrentProperty' - ) { - diagnostic.withDetails({ - kind: 'hint', - message: `Hint: If this value is a Ref (value returned by \`useRef()\`), rename the variable to end in "Ref".`, + effect.reason?.kind === 'AssignCurrentProperty' && + isRefOrRefValue(effect.value.identifier); + + if (!isLegitimateRefAssignment) { + const reason = getWriteErrorReason({ + kind: value.kind, + reason: value.reason, + }); + const variable = + effect.value.identifier.name !== null && + effect.value.identifier.name.kind === 'named' + ? `\`${effect.value.identifier.name.value}\`` + : 'value'; + const diagnostic = CompilerDiagnostic.create({ + category: ErrorCategory.Immutability, + reason: 'This value cannot be modified', + description: reason, + }).withDetails({ + kind: 'error', + loc: effect.value.loc, + message: `${variable} cannot be modified`, + }); + if ( + effect.kind === 'Mutate' && + effect.reason?.kind === 'AssignCurrentProperty' + ) { + diagnostic.withDetails({ + kind: 'hint', + message: `Hint: If this value is a Ref (value returned by \`useRef()\`), rename the variable to end in "Ref".`, + }); + } + effects.push({ + kind: 'MutateFrozen', + place: effect.value, + error: diagnostic, }); } - effects.push({ - kind: 'MutateFrozen', - place: effect.value, - error: diagnostic, - }); + break; } } } @@ -1303,47 +1313,56 @@ function applyEffect( effects, ); } else { - const reason = getWriteErrorReason({ - kind: value.kind, - reason: value.reason, - }); - const variable = - effect.value.identifier.name !== null && - effect.value.identifier.name.kind === 'named' - ? `\`${effect.value.identifier.name.value}\`` - : 'value'; - const diagnostic = CompilerDiagnostic.create({ - category: ErrorCategory.Immutability, - reason: 'This value cannot be modified', - description: reason, - }).withDetails({ - kind: 'error', - loc: effect.value.loc, - message: `${variable} cannot be modified`, - }); - if ( + // Special case: assigning to ref.current is allowed even if ref is an argument + // This is the intended use of refs and should not be flagged as an error + const isLegitimateRefAssignment = effect.kind === 'Mutate' && - effect.reason?.kind === 'AssignCurrentProperty' - ) { - diagnostic.withDetails({ - kind: 'hint', - message: `Hint: If this value is a Ref (value returned by \`useRef()\`), rename the variable to end in "Ref".`, + effect.reason?.kind === 'AssignCurrentProperty' && + isRefOrRefValue(effect.value.identifier); + + if (!isLegitimateRefAssignment) { + const reason = getWriteErrorReason({ + kind: value.kind, + reason: value.reason, }); + const variable = + effect.value.identifier.name !== null && + effect.value.identifier.name.kind === 'named' + ? `\`${effect.value.identifier.name.value}\`` + : 'value'; + const diagnostic = CompilerDiagnostic.create({ + category: ErrorCategory.Immutability, + reason: 'This value cannot be modified', + description: reason, + }).withDetails({ + kind: 'error', + loc: effect.value.loc, + message: `${variable} cannot be modified`, + }); + if ( + effect.kind === 'Mutate' && + effect.reason?.kind === 'AssignCurrentProperty' + ) { + diagnostic.withDetails({ + kind: 'hint', + message: `Hint: If this value is a Ref (value returned by \`useRef()\`), rename the variable to end in "Ref".`, + }); + } + applyEffect( + context, + state, + { + kind: + value.kind === ValueKind.Frozen + ? 'MutateFrozen' + : 'MutateGlobal', + place: effect.value, + error: diagnostic, + }, + initialized, + effects, + ); } - applyEffect( - context, - state, - { - kind: - value.kind === ValueKind.Frozen - ? 'MutateFrozen' - : 'MutateGlobal', - place: effect.value, - error: diagnostic, - }, - initialized, - effects, - ); } } break; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/forwarded-ref-current-assignment.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/forwarded-ref-current-assignment.js new file mode 100644 index 00000000000..aed6c46da3a --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/forwarded-ref-current-assignment.js @@ -0,0 +1,30 @@ +// Test case for issue #34955 +// Assigning to ref.current should be allowed for forwarded refs +import {useCallback, useState} from 'react'; + +function useContainerWidth(ref) { + const [containerWidth, setContainerWidth] = useState(0); + + const containerRef = useCallback( + node => { + if (typeof ref === 'function') { + ref(node); + } else if (ref) { + // This is a legitimate ref.current assignment and should not error + ref.current = node; + } + if (node !== null) { + setContainerWidth(node.offsetWidth); + } + }, + [ref], + ); + + return {containerRef, containerWidth}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useContainerWidth, + params: [{current: null}], +}; +