From c2326b1336e38a52899fad6c2ddbb71ea7ddd3ee Mon Sep 17 00:00:00 2001
From: Joseph Savona <6425824+josephsavona@users.noreply.github.com>
Date: Tue, 29 Jul 2025 10:56:04 -0700
Subject: [PATCH] [compiler] disallow ref access in state initializer,
reducer/initializer (#34025)
Per title, disallow ref access in `useState()` initializer function,
`useReducer()` reducer, and `useReducer()` init function.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34025).
* #34027
* #34026
* __->__ #34025
---
.../Validation/ValidateNoRefAccessInRender.ts | 7 ++-
...valid-access-ref-in-reducer-init.expect.md | 45 +++++++++++++++++++
...rror.invalid-access-ref-in-reducer-init.js | 17 +++++++
...or.invalid-access-ref-in-reducer.expect.md | 41 +++++++++++++++++
.../error.invalid-access-ref-in-reducer.js | 13 ++++++
...-access-ref-in-state-initializer.expect.md | 41 +++++++++++++++++
...invalid-access-ref-in-state-initializer.js | 13 ++++++
7 files changed, 176 insertions(+), 1 deletion(-)
create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-reducer-init.expect.md
create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-reducer-init.js
create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-reducer.expect.md
create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-reducer.js
create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-state-initializer.expect.md
create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-state-initializer.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 70aa4eeea8..c10d1bc07e 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts
+++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts
@@ -434,7 +434,12 @@ function validateNoRefAccessInRenderImpl(
* By default we check that function call operands are not refs,
* ref values, or functions that can access refs.
*/
- if (isRefLValue || hookKind != null) {
+ if (
+ isRefLValue ||
+ (hookKind != null &&
+ hookKind !== 'useState' &&
+ hookKind !== 'useReducer')
+ ) {
/**
* Special cases:
*
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-reducer-init.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-reducer-init.expect.md
new file mode 100644
index 0000000000..29fe24a220
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-reducer-init.expect.md
@@ -0,0 +1,45 @@
+
+## Input
+
+```javascript
+import {useReducer, useRef} from 'react';
+
+function Component(props) {
+ const ref = useRef(props.value);
+ const [state] = useReducer(
+ (state, action) => state + action,
+ 0,
+ init => ref.current
+ );
+
+ return ;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{value: 42}],
+};
+
+```
+
+
+## Error
+
+```
+Found 1 error:
+
+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-access-ref-in-reducer-init.ts:8:4
+ 6 | (state, action) => state + action,
+ 7 | 0,
+> 8 | init => ref.current
+ | ^^^^^^^^^^^^^^^^^^^ Passing a ref to a function may read its value during render
+ 9 | );
+ 10 |
+ 11 | return ;
+```
+
+
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-reducer-init.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-reducer-init.js
new file mode 100644
index 0000000000..df10b8a9eb
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-reducer-init.js
@@ -0,0 +1,17 @@
+import {useReducer, useRef} from 'react';
+
+function Component(props) {
+ const ref = useRef(props.value);
+ const [state] = useReducer(
+ (state, action) => state + action,
+ 0,
+ init => ref.current
+ );
+
+ return ;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{value: 42}],
+};
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-reducer.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-reducer.expect.md
new file mode 100644
index 0000000000..f23560b4f6
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-reducer.expect.md
@@ -0,0 +1,41 @@
+
+## Input
+
+```javascript
+import {useReducer, useRef} from 'react';
+
+function Component(props) {
+ const ref = useRef(props.value);
+ const [state] = useReducer(() => ref.current, null);
+
+ return ;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{value: 42}],
+};
+
+```
+
+
+## Error
+
+```
+Found 1 error:
+
+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-access-ref-in-reducer.ts:5:29
+ 3 | function Component(props) {
+ 4 | const ref = useRef(props.value);
+> 5 | const [state] = useReducer(() => ref.current, null);
+ | ^^^^^^^^^^^^^^^^^ Passing a ref to a function may read its value during render
+ 6 |
+ 7 | return ;
+ 8 | }
+```
+
+
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-reducer.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-reducer.js
new file mode 100644
index 0000000000..135a78e0ba
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-reducer.js
@@ -0,0 +1,13 @@
+import {useReducer, useRef} from 'react';
+
+function Component(props) {
+ const ref = useRef(props.value);
+ const [state] = useReducer(() => ref.current, null);
+
+ return ;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{value: 42}],
+};
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-state-initializer.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-state-initializer.expect.md
new file mode 100644
index 0000000000..dd6a64d9db
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-state-initializer.expect.md
@@ -0,0 +1,41 @@
+
+## Input
+
+```javascript
+import {useRef, useState} from 'react';
+
+function Component(props) {
+ const ref = useRef(props.value);
+ const [state] = useState(() => ref.current);
+
+ return ;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{value: 42}],
+};
+
+```
+
+
+## Error
+
+```
+Found 1 error:
+
+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-access-ref-in-state-initializer.ts:5:27
+ 3 | function Component(props) {
+ 4 | const ref = useRef(props.value);
+> 5 | const [state] = useState(() => ref.current);
+ | ^^^^^^^^^^^^^^^^^ Passing a ref to a function may read its value during render
+ 6 |
+ 7 | return ;
+ 8 | }
+```
+
+
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-state-initializer.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-state-initializer.js
new file mode 100644
index 0000000000..c3f233023e
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-state-initializer.js
@@ -0,0 +1,13 @@
+import {useRef, useState} from 'react';
+
+function Component(props) {
+ const ref = useRef(props.value);
+ const [state] = useState(() => ref.current);
+
+ return ;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{value: 42}],
+};