diff --git a/compiler/packages/babel-plugin-react-compiler/src/TypeInference/InferTypes.ts b/compiler/packages/babel-plugin-react-compiler/src/TypeInference/InferTypes.ts index 73088fd8521..488d988b971 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/TypeInference/InferTypes.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/TypeInference/InferTypes.ts @@ -451,6 +451,18 @@ function* generateInstructionTypes( case 'JsxExpression': case 'JsxFragment': { + if (env.config.enableTreatRefLikeIdentifiersAsRefs) { + if (value.kind === 'JsxExpression') { + for (const prop of value.props) { + if (prop.kind === 'JsxAttribute' && prop.name === 'ref') { + yield equation(prop.place.identifier.type, { + kind: 'Object', + shapeId: BuiltInUseRefId, + }); + } + } + } + } yield equation(left, {kind: 'Object', shapeId: BuiltInJsxId}); break; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts index cb78fc1d874..b4fb0d171ac 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts @@ -407,15 +407,28 @@ function validateNoRefAccessInRenderImpl( ); } } + /* + * If we already reported an error on this instruction, don't report + * duplicate errors + */ if (!didError) { - /* - * If we already reported an error on this instruction, don't report - * duplicate errors - */ + const isRefLValue = isUseRefType(instr.lvalue.identifier); for (const operand of eachInstructionValueOperand(instr.value)) { if (hookKind != null) { validateNoDirectRefValueAccess(errors, operand, env); - } else { + } else if (!isRefLValue) { + /** + * In general passing a ref to a function may access that ref + * value during render, so we disallow it. + * + * The main exception is the "mergeRefs" pattern, ie a function + * that accepts multiple refs as arguments (or an array of refs) + * and returns a new, aggregated ref. If the lvalue is a ref, + * we assume that the user is doing this pattern and allow passing + * refs. + * + * Eg `const mergedRef = mergeRefs(ref1, ref2)` + */ validateNoRefPassedToFunction( errors, env, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-merge-refs-pattern.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-merge-refs-pattern.expect.md new file mode 100644 index 00000000000..6933edef467 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-merge-refs-pattern.expect.md @@ -0,0 +1,44 @@ + +## Input + +```javascript +// @enableTreatRefLikeIdentifiersAsRefs @validateRefAccessDuringRender + +import {useRef} from 'react'; + +function Component() { + const ref = useRef(null); + const ref2 = useRef(null); + const mergedRef = mergeRefs([ref], ref2); + + return ; +} + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @enableTreatRefLikeIdentifiersAsRefs @validateRefAccessDuringRender + +import { useRef } from "react"; + +function Component() { + const $ = _c(1); + const ref = useRef(null); + const ref2 = useRef(null); + const mergedRef = mergeRefs([ref], ref2); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = ; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +``` + +### Eval output +(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-merge-refs-pattern.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-merge-refs-pattern.js new file mode 100644 index 00000000000..91c5f082849 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-merge-refs-pattern.js @@ -0,0 +1,11 @@ +// @enableTreatRefLikeIdentifiersAsRefs @validateRefAccessDuringRender + +import {useRef} from 'react'; + +function Component() { + const ref = useRef(null); + const ref2 = useRef(null); + const mergedRef = mergeRefs([ref], ref2); + + return ; +}