[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
Joseph Savona committed
Jul 29, 2025 at 10:56 UTC
c2326b1336e38a52899fad6c2ddbb71ea7ddd3ee
7 files changed
+176
-1
compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts
+6
-1
@@ -434,7 +434,12 @@ function validateNoRefAccessInRenderImpl(
434
* By default we check that function call operands are not refs,
435
* ref values, or functions that can access refs.
436
*/
437
- if (isRefLValue || hookKind != null) {
437
+ if (
438
+ isRefLValue ||
439
+ (hookKind != null &&
440
+ hookKind !== 'useState' &&
441
+ hookKind !== 'useReducer')
442
+ ) {
443
/**
444
* Special cases:
445
*
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-reducer-init.expect.md
new
+45
@@ -0,0 +1,45 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+import {useReducer, useRef} from 'react';
6
+
7
+function Component(props) {
8
+ const ref = useRef(props.value);
9
+ const [state] = useReducer(
10
+ (state, action) => state + action,
11
+ 0,
12
+ init => ref.current
13
+ );
14
+
15
+ return <Stringify state={state} />;
16
+}
17
+
18
+export const FIXTURE_ENTRYPOINT = {
19
+ fn: Component,
20
+ params: [{value: 42}],
21
+};
22
+
23
+```
24
+
25
+
26
+## Error
27
+
28
+```
29
+Found 1 error:
30
+
31
+Error: Cannot access refs during render
32
+
33
+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)
34
+
35
+error.invalid-access-ref-in-reducer-init.ts:8:4
36
+ 6 | (state, action) => state + action,
37
+ 7 | 0,
38
+> 8 | init => ref.current
39
+ | ^^^^^^^^^^^^^^^^^^^ Passing a ref to a function may read its value during render
40
+ 9 | );
41
+ 10 |
42
+ 11 | return <Stringify state={state} />;
43
+```
44
+
45
+
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-reducer-init.js
new
+17
@@ -0,0 +1,17 @@
1
+import {useReducer, useRef} from 'react';
2
+
3
+function Component(props) {
4
+ const ref = useRef(props.value);
5
+ const [state] = useReducer(
6
+ (state, action) => state + action,
7
+ 0,
8
+ init => ref.current
9
+ );
10
+
11
+ return <Stringify state={state} />;
12
+}
13
+
14
+export const FIXTURE_ENTRYPOINT = {
15
+ fn: Component,
16
+ params: [{value: 42}],
17
+};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-reducer.expect.md
new
+41
@@ -0,0 +1,41 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+import {useReducer, useRef} from 'react';
6
+
7
+function Component(props) {
8
+ const ref = useRef(props.value);
9
+ const [state] = useReducer(() => ref.current, null);
10
+
11
+ return <Stringify state={state} />;
12
+}
13
+
14
+export const FIXTURE_ENTRYPOINT = {
15
+ fn: Component,
16
+ params: [{value: 42}],
17
+};
18
+
19
+```
20
+
21
+
22
+## Error
23
+
24
+```
25
+Found 1 error:
26
+
27
+Error: Cannot access refs during render
28
+
29
+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)
30
+
31
+error.invalid-access-ref-in-reducer.ts:5:29
32
+ 3 | function Component(props) {
33
+ 4 | const ref = useRef(props.value);
34
+> 5 | const [state] = useReducer(() => ref.current, null);
35
+ | ^^^^^^^^^^^^^^^^^ Passing a ref to a function may read its value during render
36
+ 6 |
37
+ 7 | return <Stringify state={state} />;
38
+ 8 | }
39
+```
40
+
41
+
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-reducer.js
new
+13
@@ -0,0 +1,13 @@
1
+import {useReducer, useRef} from 'react';
2
+
3
+function Component(props) {
4
+ const ref = useRef(props.value);
5
+ const [state] = useReducer(() => ref.current, null);
6
+
7
+ return <Stringify state={state} />;
8
+}
9
+
10
+export const FIXTURE_ENTRYPOINT = {
11
+ fn: Component,
12
+ params: [{value: 42}],
13
+};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-state-initializer.expect.md
new
+41
@@ -0,0 +1,41 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+import {useRef, useState} from 'react';
6
+
7
+function Component(props) {
8
+ const ref = useRef(props.value);
9
+ const [state] = useState(() => ref.current);
10
+
11
+ return <Stringify state={state} />;
12
+}
13
+
14
+export const FIXTURE_ENTRYPOINT = {
15
+ fn: Component,
16
+ params: [{value: 42}],
17
+};
18
+
19
+```
20
+
21
+
22
+## Error
23
+
24
+```
25
+Found 1 error:
26
+
27
+Error: Cannot access refs during render
28
+
29
+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)
30
+
31
+error.invalid-access-ref-in-state-initializer.ts:5:27
32
+ 3 | function Component(props) {
33
+ 4 | const ref = useRef(props.value);
34
+> 5 | const [state] = useState(() => ref.current);
35
+ | ^^^^^^^^^^^^^^^^^ Passing a ref to a function may read its value during render
36
+ 6 |
37
+ 7 | return <Stringify state={state} />;
38
+ 8 | }
39
+```
40
+
41
+
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-in-state-initializer.js
new
+13
@@ -0,0 +1,13 @@
1
+import {useRef, useState} from 'react';
2
+
3
+function Component(props) {
4
+ const ref = useRef(props.value);
5
+ const [state] = useState(() => ref.current);
6
+
7
+ return <Stringify state={state} />;
8
+}
9
+
10
+export const FIXTURE_ENTRYPOINT = {
11
+ fn: Component,
12
+ params: [{value: 42}],
13
+};