From 3f4f38347eca97f07fc94f7c0686cca5305c3605 Mon Sep 17 00:00:00 2001 From: fr3y123 Date: Fri, 3 Apr 2026 21:22:04 +0300 Subject: [PATCH 1/3] Fix refs validation for forwarded ref props --- .../Validation/ValidateNoRefAccessInRender.ts | 37 ++++++++++++++++++- ...allow-forwarding-ref-prop-to-jsx.expect.md | 32 ++++++++++++++++ .../allow-forwarding-ref-prop-to-jsx.js | 4 ++ 3 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-forwarding-ref-prop-to-jsx.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-forwarding-ref-prop-to-jsx.js 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 7da564205475..2d1d05bfb29d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts @@ -356,8 +356,41 @@ function validateNoRefAccessInRenderImpl( switch (instr.value.kind) { case 'JsxExpression': case 'JsxFragment': { - for (const operand of eachInstructionValueOperand(instr.value)) { - validateNoDirectRefValueAccess(errors, operand, env); + if (instr.value.kind === 'JsxExpression') { + if (instr.value.tag.kind === 'Identifier') { + validateNoDirectRefValueAccess(errors, instr.value.tag, env); + } + for (const attribute of instr.value.props) { + switch (attribute.kind) { + case 'JsxAttribute': { + if (attribute.name !== 'ref') { + validateNoDirectRefValueAccess( + errors, + attribute.place, + env, + ); + } + break; + } + case 'JsxSpreadAttribute': { + validateNoDirectRefValueAccess( + errors, + attribute.argument, + env, + ); + break; + } + } + } + if (instr.value.children != null) { + for (const child of instr.value.children) { + validateNoDirectRefValueAccess(errors, child, env); + } + } + } else { + for (const operand of eachInstructionValueOperand(instr.value)) { + validateNoDirectRefValueAccess(errors, operand, env); + } } break; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-forwarding-ref-prop-to-jsx.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-forwarding-ref-prop-to-jsx.expect.md new file mode 100644 index 000000000000..d9e350933e4c --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-forwarding-ref-prop-to-jsx.expect.md @@ -0,0 +1,32 @@ + +## Input + +```javascript +// @validateRefAccessDuringRender @compilationMode:"infer" +function TextArea(props) { + return ; +} + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @validateRefAccessDuringRender @compilationMode:"infer" +function TextArea(props) { + const $ = _c(2); + let t0; + if ($[0] !== props.ref) { + t0 = ; + $[0] = props.ref; + $[1] = t0; + } else { + t0 = $[1]; + } + 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-forwarding-ref-prop-to-jsx.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-forwarding-ref-prop-to-jsx.js new file mode 100644 index 000000000000..e5a5f580d755 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-forwarding-ref-prop-to-jsx.js @@ -0,0 +1,4 @@ +// @validateRefAccessDuringRender @compilationMode:"infer" +function TextArea(props) { + return ; +} From 39df7c136d00ee71d46cd0eb1cf0807d87fdaac8 Mon Sep 17 00:00:00 2001 From: fr3y123 Date: Fri, 3 Apr 2026 21:37:19 +0300 Subject: [PATCH 2/3] update --- .tmp-pr-body-35997-update.md | 66 ++++++++++++++++++++++++++++++++++++ .tmp-pr-body-35997.md | 64 ++++++++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 .tmp-pr-body-35997-update.md create mode 100644 .tmp-pr-body-35997.md diff --git a/.tmp-pr-body-35997-update.md b/.tmp-pr-body-35997-update.md new file mode 100644 index 000000000000..fd2e244f0004 --- /dev/null +++ b/.tmp-pr-body-35997-update.md @@ -0,0 +1,66 @@ +Fixes #35997 + +## Summary + +This fixes a false positive in `react-hooks/refs` when forwarding a `ref` prop directly to JSX. + +Previously, code like: + +```tsx +function TextArea(props) { + return ; +} +``` + +was reported as: + +`Cannot access refs during render` + +even though the component was only forwarding the ref object to JSX and not reading `ref.current`. + +The root cause was that `ValidateNoRefAccessInRender` validated all JSX attribute operands uniformly. That was too strict for the special `ref` prop, where passing a ref object through to JSX should be allowed. + +This change teaches the validator to treat JSX `ref` attributes specially: + +- still validate the JSX tag +- still validate non-`ref` JSX attributes +- still validate spread attributes and children +- skip direct-ref-value validation for the `ref` attribute itself + +This preserves the existing error for actual render-time ref reads like `props.ref.current`, while allowing valid ref forwarding patterns. + +## How did you test this change? + +Reproduced the issue with a focused compiler fixture: + +1. Add a fixture containing: + +```js +// @validateRefAccessDuringRender @compilationMode:"infer" +function TextArea(props) { + return ; +} +``` + +2. Run: + +```bash +corepack yarn workspace babel-plugin-react-compiler snap --pattern allow-forwarding-ref-prop-to-jsx --sync +``` + +Before the fix, this failed with: + +```text +Error: Cannot access refs during render +... +return ; + ^^^^^^^^^ Cannot access ref value during render +``` + +After the fix, the same targeted fixture passes: + +```bash +corepack yarn workspace babel-plugin-react-compiler snap --pattern allow-forwarding-ref-prop-to-jsx --sync +``` + +I also checked the existing invalid fixture for reading `props.ref.current` to confirm the validator still rejects direct ref-value access during render. diff --git a/.tmp-pr-body-35997.md b/.tmp-pr-body-35997.md new file mode 100644 index 000000000000..9fec9ad6a97e --- /dev/null +++ b/.tmp-pr-body-35997.md @@ -0,0 +1,64 @@ +## Summary + +This fixes a false positive in `react-hooks/refs` when forwarding a `ref` prop directly to JSX. + +Previously, code like: + +```tsx +function TextArea(props) { + return ; +} +``` + +was reported as: + +`Cannot access refs during render` + +even though the component was only forwarding the ref object to JSX and not reading `ref.current`. + +The root cause was that `ValidateNoRefAccessInRender` validated all JSX attribute operands uniformly. That was too strict for the special `ref` prop, where passing a ref object through to JSX should be allowed. + +This change teaches the validator to treat JSX `ref` attributes specially: + +- still validate the JSX tag +- still validate non-`ref` JSX attributes +- still validate spread attributes and children +- skip direct-ref-value validation for the `ref` attribute itself + +This preserves the existing error for actual render-time ref reads like `props.ref.current`, while allowing valid ref forwarding patterns. + +## How did you test this change? + +Reproduced the issue with a focused compiler fixture: + +1. Add a fixture containing: + +```js +// @validateRefAccessDuringRender @compilationMode:"infer" +function TextArea(props) { + return ; +} +``` + +2. Run: + +```bash +corepack yarn workspace babel-plugin-react-compiler snap --pattern allow-forwarding-ref-prop-to-jsx --sync +``` + +Before the fix, this failed with: + +```text +Error: Cannot access refs during render +... +return ; + ^^^^^^^^^ Cannot access ref value during render +``` + +After the fix, the same targeted fixture passes: + +```bash +corepack yarn workspace babel-plugin-react-compiler snap --pattern allow-forwarding-ref-prop-to-jsx --sync +``` + +I also checked the existing invalid fixture for reading `props.ref.current` to confirm the validator still rejects direct ref-value access during render. From ddcc3c87f97a589e2a91cc4156814eb757485363 Mon Sep 17 00:00:00 2001 From: fr3y123 Date: Fri, 3 Apr 2026 21:38:59 +0300 Subject: [PATCH 3/3] del .tmp files --- .tmp-pr-body-35997-update.md | 66 ------------------------------------ .tmp-pr-body-35997.md | 64 ---------------------------------- 2 files changed, 130 deletions(-) delete mode 100644 .tmp-pr-body-35997-update.md delete mode 100644 .tmp-pr-body-35997.md diff --git a/.tmp-pr-body-35997-update.md b/.tmp-pr-body-35997-update.md deleted file mode 100644 index fd2e244f0004..000000000000 --- a/.tmp-pr-body-35997-update.md +++ /dev/null @@ -1,66 +0,0 @@ -Fixes #35997 - -## Summary - -This fixes a false positive in `react-hooks/refs` when forwarding a `ref` prop directly to JSX. - -Previously, code like: - -```tsx -function TextArea(props) { - return ; -} -``` - -was reported as: - -`Cannot access refs during render` - -even though the component was only forwarding the ref object to JSX and not reading `ref.current`. - -The root cause was that `ValidateNoRefAccessInRender` validated all JSX attribute operands uniformly. That was too strict for the special `ref` prop, where passing a ref object through to JSX should be allowed. - -This change teaches the validator to treat JSX `ref` attributes specially: - -- still validate the JSX tag -- still validate non-`ref` JSX attributes -- still validate spread attributes and children -- skip direct-ref-value validation for the `ref` attribute itself - -This preserves the existing error for actual render-time ref reads like `props.ref.current`, while allowing valid ref forwarding patterns. - -## How did you test this change? - -Reproduced the issue with a focused compiler fixture: - -1. Add a fixture containing: - -```js -// @validateRefAccessDuringRender @compilationMode:"infer" -function TextArea(props) { - return ; -} -``` - -2. Run: - -```bash -corepack yarn workspace babel-plugin-react-compiler snap --pattern allow-forwarding-ref-prop-to-jsx --sync -``` - -Before the fix, this failed with: - -```text -Error: Cannot access refs during render -... -return ; - ^^^^^^^^^ Cannot access ref value during render -``` - -After the fix, the same targeted fixture passes: - -```bash -corepack yarn workspace babel-plugin-react-compiler snap --pattern allow-forwarding-ref-prop-to-jsx --sync -``` - -I also checked the existing invalid fixture for reading `props.ref.current` to confirm the validator still rejects direct ref-value access during render. diff --git a/.tmp-pr-body-35997.md b/.tmp-pr-body-35997.md deleted file mode 100644 index 9fec9ad6a97e..000000000000 --- a/.tmp-pr-body-35997.md +++ /dev/null @@ -1,64 +0,0 @@ -## Summary - -This fixes a false positive in `react-hooks/refs` when forwarding a `ref` prop directly to JSX. - -Previously, code like: - -```tsx -function TextArea(props) { - return ; -} -``` - -was reported as: - -`Cannot access refs during render` - -even though the component was only forwarding the ref object to JSX and not reading `ref.current`. - -The root cause was that `ValidateNoRefAccessInRender` validated all JSX attribute operands uniformly. That was too strict for the special `ref` prop, where passing a ref object through to JSX should be allowed. - -This change teaches the validator to treat JSX `ref` attributes specially: - -- still validate the JSX tag -- still validate non-`ref` JSX attributes -- still validate spread attributes and children -- skip direct-ref-value validation for the `ref` attribute itself - -This preserves the existing error for actual render-time ref reads like `props.ref.current`, while allowing valid ref forwarding patterns. - -## How did you test this change? - -Reproduced the issue with a focused compiler fixture: - -1. Add a fixture containing: - -```js -// @validateRefAccessDuringRender @compilationMode:"infer" -function TextArea(props) { - return ; -} -``` - -2. Run: - -```bash -corepack yarn workspace babel-plugin-react-compiler snap --pattern allow-forwarding-ref-prop-to-jsx --sync -``` - -Before the fix, this failed with: - -```text -Error: Cannot access refs during render -... -return ; - ^^^^^^^^^ Cannot access ref value during render -``` - -After the fix, the same targeted fixture passes: - -```bash -corepack yarn workspace babel-plugin-react-compiler snap --pattern allow-forwarding-ref-prop-to-jsx --sync -``` - -I also checked the existing invalid fixture for reading `props.ref.current` to confirm the validator still rejects direct ref-value access during render.