From ca9ad20bb0adf39b94145822378b2f15ba54dc9c Mon Sep 17 00:00:00 2001 From: Apoorv Darshan Date: Thu, 19 Feb 2026 02:30:06 +0530 Subject: [PATCH] [compiler] Fix false positive in refs rule for refs stored in objects When multiple refs are stored in an object (e.g. `{group1: useRef(), group2: useRef()}`), the `react-hooks/refs` validation incorrectly reported "Cannot access ref value during render" on the second property access. This happened because `collectTemporariesSidemap` aliased PropertyLoad results to the source object, causing `env.set` on the property load lvalue to corrupt the object's type through the temporary chain. The fix removes PropertyLoad aliasing from `collectTemporariesSidemap` (the main validation loop already tracks types correctly through `env.set`), and adjusts the PropertyLoad case in the main validation to propagate function type info from Structure types when the value is null. Fixes #35813 --- .../Validation/ValidateNoRefAccessInRender.ts | 12 +---- ...-passing-refs-in-object-as-props.expect.md | 52 +++++++++++++++++++ .../allow-passing-refs-in-object-as-props.js | 12 +++++ ...f-added-to-dep-without-type-info.expect.md | 11 ++-- 4 files changed, 71 insertions(+), 16 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-passing-refs-in-object-as-props.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-passing-refs-in-object-as-props.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 dd7b04a11d69..d17a62e31191 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts @@ -151,16 +151,6 @@ function collectTemporariesSidemap(fn: HIRFunction, env: Env): void { break; } case 'PropertyLoad': { - if ( - isUseRefType(value.object.identifier) && - value.property === 'current' - ) { - continue; - } - const temp = env.lookup(value.object); - if (temp != null) { - env.define(lvalue, temp); - } break; } } @@ -375,7 +365,7 @@ function validateNoRefAccessInRenderImpl( const objType = env.get(instr.value.object.identifier.id); let lookupType: null | RefAccessType = null; if (objType?.kind === 'Structure') { - lookupType = objType.value; + lookupType = objType.value ?? objType; } else if (objType?.kind === 'Ref') { lookupType = { kind: 'RefValue', diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-passing-refs-in-object-as-props.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-passing-refs-in-object-as-props.expect.md new file mode 100644 index 000000000000..1debc491b211 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-passing-refs-in-object-as-props.expect.md @@ -0,0 +1,52 @@ + +## Input + +```javascript +function Component() { + const groupRefs = { + group1: useRef(null), + group2: useRef(null), + }; + return ( + <> + + + + ); +} + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +function Component() { + const $ = _c(2); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = { group1: useRef(null), group2: useRef(null) }; + $[0] = t0; + } else { + t0 = $[0]; + } + const groupRefs = t0; + let t1; + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = ( + <> + + + + ); + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +``` + +### 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-passing-refs-in-object-as-props.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-passing-refs-in-object-as-props.js new file mode 100644 index 000000000000..7b62b0d35cca --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-passing-refs-in-object-as-props.js @@ -0,0 +1,12 @@ +function Component() { + const groupRefs = { + group1: useRef(null), + group2: useRef(null), + }; + return ( + <> + + + + ); +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-use-ref-added-to-dep-without-type-info.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-use-ref-added-to-dep-without-type-info.expect.md index 53bf66b1ee5a..bf5c4011a5fd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-use-ref-added-to-dep-without-type-info.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-use-ref-added-to-dep-without-type-info.expect.md @@ -41,13 +41,14 @@ Error: Cannot access refs during render React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef). -error.invalid-use-ref-added-to-dep-without-type-info.ts:12:28 - 10 | const x = {a, val: val.ref.current}; +error.invalid-use-ref-added-to-dep-without-type-info.ts:10:21 + 8 | // however, this is an instance of accessing a ref during render and is disallowed + 9 | // under React's rules, so we reject this input +> 10 | const x = {a, val: val.ref.current}; + | ^^^^^^^^^^^^^^^ Cannot access ref value during render 11 | -> 12 | return ; - | ^ Cannot access ref value during render + 12 | return ; 13 | } - 14 | ``` \ No newline at end of file