diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccesInRender.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccesInRender.ts index 4da6b23a1a9ed..df6241a73f448 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccesInRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccesInRender.ts @@ -59,20 +59,7 @@ function validateNoRefAccessInRenderImpl( case 'JsxExpression': case 'JsxFragment': { for (const operand of eachInstructionValueOperand(instr.value)) { - if (isRefValueType(operand.identifier)) { - errors.push({ - severity: ErrorSeverity.InvalidReact, - reason: - 'Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)', - loc: lookupLocations.get(operand.identifier.id) ?? operand.loc, - description: - operand.identifier.name !== null && - operand.identifier.name.kind === 'named' - ? `Cannot access ref value \`${operand.identifier.name.value}\`` - : null, - suggestions: null, - }); - } + validateNoDirectRefValueAccess(errors, operand, lookupLocations); } break; } @@ -232,12 +219,17 @@ function validateNoRefAccessInRenderImpl( } } for (const operand of eachTerminalOperand(block.terminal)) { - validateNoRefValueAccess( - errors, - refAccessingFunctions, - lookupLocations, - operand, - ); + if (block.terminal.kind !== 'return') { + validateNoRefValueAccess( + errors, + refAccessingFunctions, + lookupLocations, + operand, + ); + } else { + // Allow functions containing refs to be returned, but not direct ref values + validateNoDirectRefValueAccess(errors, operand, lookupLocations); + } } } @@ -297,3 +289,24 @@ function validateNoRefAccess( }); } } + +function validateNoDirectRefValueAccess( + errors: CompilerError, + operand: Place, + lookupLocations: Map, +): void { + if (isRefValueType(operand.identifier)) { + errors.push({ + severity: ErrorSeverity.InvalidReact, + reason: + 'Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)', + loc: lookupLocations.get(operand.identifier.id) ?? operand.loc, + description: + operand.identifier.name !== null && + operand.identifier.name.kind === 'named' + ? `Cannot access ref value \`${operand.identifier.name.value}\`` + : null, + suggestions: null, + }); + } +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.return-ref-callback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.return-ref-callback.expect.md deleted file mode 100644 index 98f2a86714034..0000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.return-ref-callback.expect.md +++ /dev/null @@ -1,37 +0,0 @@ - -## Input - -```javascript -// @flow @validateRefAccessDuringRender @validatePreserveExistingMemoizationGuarantees - -component Foo() { - const ref = useRef(); - - const s = () => { - return ref.current; - }; - - return s; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [], -}; - -``` - - -## Error - -``` - 8 | }; - 9 | -> 10 | return s; - | ^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (10:10) - 11 | } - 12 | - 13 | export const FIXTURE_ENTRYPOINT = { -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/return-ref-callback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/return-ref-callback.expect.md new file mode 100644 index 0000000000000..ed1dfa39ea55c --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/return-ref-callback.expect.md @@ -0,0 +1,55 @@ + +## Input + +```javascript +// @flow @validateRefAccessDuringRender @validatePreserveExistingMemoizationGuarantees + +import {useRef} from 'react'; + +component Foo() { + const ref = useRef(); + + const s = () => { + return ref.current; + }; + + return s; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; + +import { useRef } from "react"; + +function Foo() { + const $ = _c(1); + const ref = useRef(); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = () => ref.current; + $[0] = t0; + } else { + t0 = $[0]; + } + const s = t0; + return s; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [], +}; + +``` + +### Eval output +(kind: ok) "[[ function params=0 ]]" \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.return-ref-callback.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/return-ref-callback.js similarity index 89% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.return-ref-callback.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/return-ref-callback.js index 3a650cdeed8e8..f1a45ebc4ff3e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.return-ref-callback.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/return-ref-callback.js @@ -1,5 +1,7 @@ // @flow @validateRefAccessDuringRender @validatePreserveExistingMemoizationGuarantees +import {useRef} from 'react'; + component Foo() { const ref = useRef();