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 abbb7d847698..541ad0dda4b7 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts
+++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts
@@ -655,33 +655,15 @@ function validateNoRefAccessInRenderImpl(
if (instr.value.operator === '!') {
const value = env.get(instr.value.value.identifier.id);
const refId =
- value?.kind === 'RefValue' && value.refId != null
- ? value.refId
+ value?.kind === 'RefValue'
+ ? (value.refId ?? nextRefId())
: null;
if (refId !== null) {
/*
- * Record an error suggesting the `if (ref.current == null)` pattern,
- * but also record the lvalue as a guard so that we don't emit a second
- * error for the write to the ref
+ * Allow !ref.current patterns for ref initialization as they are
+ * semantically equivalent to ref.current == null checks
*/
env.set(instr.lvalue.identifier.id, {kind: 'Guard', refId});
- errors.pushDiagnostic(
- CompilerDiagnostic.create({
- category: ErrorCategory.Refs,
- reason: 'Cannot access refs during render',
- description: ERROR_DESCRIPTION,
- })
- .withDetails({
- kind: 'error',
- loc: instr.value.value.loc,
- message: `Cannot access ref value during render`,
- })
- .withDetails({
- kind: 'hint',
- message:
- 'To initialize a ref only once, check that the ref is null with the pattern `if (ref.current == null) { ref.current = ... }`',
- }),
- );
break;
}
}
@@ -693,18 +675,21 @@ function validateNoRefAccessInRenderImpl(
const right = env.get(instr.value.right.identifier.id);
let nullish: boolean = false;
let refId: RefId | null = null;
- if (left?.kind === 'RefValue' && left.refId != null) {
- refId = left.refId;
- } else if (right?.kind === 'RefValue' && right.refId != null) {
- refId = right.refId;
+
+ if (left?.kind === 'RefValue') {
+ refId = left.refId ?? nextRefId();
+ } else if (right?.kind === 'RefValue') {
+ refId = right.refId ?? nextRefId();
}
+ // Check for null or undefined values
if (left?.kind === 'Nullable') {
nullish = true;
} else if (right?.kind === 'Nullable') {
nullish = true;
}
+ // Allow ref comparisons with null/undefined as guards
if (refId !== null && nullish) {
env.set(instr.lvalue.identifier.id, {kind: 'Guard', refId});
} else {
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-ref-access-render-unary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-ref-access-render-unary.expect.md
index ce1be800a13b..8093488bb3d3 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-ref-access-render-unary.expect.md
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-ref-access-render-unary.expect.md
@@ -22,21 +22,7 @@ export const FIXTURE_ENTRYPOINT = {
## Error
```
-Found 4 errors:
-
-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).
-
- 4 | component C() {
- 5 | const r = useRef(null);
-> 6 | const current = !r.current;
- | ^^^^^^^^^ Cannot access ref value during render
- 7 | return
{current}
;
- 8 | }
- 9 |
-
-To initialize a ref only once, check that the ref is null with the pattern `if (ref.current == null) { ref.current = ... }`
+Found 3 errors:
Error: Cannot access refs during render
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-ref-initialization-unary-not.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-ref-initialization-unary-not.expect.md
deleted file mode 100644
index 516d006c205a..000000000000
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-ref-initialization-unary-not.expect.md
+++ /dev/null
@@ -1,43 +0,0 @@
-
-## Input
-
-```javascript
-//@flow
-import {useRef} from 'react';
-
-component C() {
- const r = useRef(null);
- if (!r.current) {
- r.current = 1;
- }
-}
-
-export const FIXTURE_ENTRYPOINT = {
- fn: C,
- params: [{}],
-};
-
-```
-
-
-## 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).
-
- 4 | component C() {
- 5 | const r = useRef(null);
-> 6 | if (!r.current) {
- | ^^^^^^^^^ Cannot access ref value during render
- 7 | r.current = 1;
- 8 | }
- 9 | }
-
-To initialize a ref only once, check that the ref is null with the pattern `if (ref.current == null) { ref.current = ... }`
-```
-
-
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-undefined-initialization-negation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-undefined-initialization-negation.expect.md
new file mode 100644
index 000000000000..2ea91e8e87af
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-undefined-initialization-negation.expect.md
@@ -0,0 +1,51 @@
+
+## Input
+
+```javascript
+import {useRef} from 'react';
+
+function Component() {
+ const ref = useRef(undefined);
+ if (!ref.current) {
+ ref.current = "initialized";
+ }
+ return Hello World
;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{}],
+};
+```
+
+## Code
+
+```javascript
+import { c as _c } from "react/compiler-runtime";
+import { useRef } from "react";
+
+function Component() {
+ const $ = _c(1);
+ const ref = useRef(undefined);
+ if (!ref.current) {
+ ref.current = "initialized";
+ }
+ let t0;
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
+ t0 = Hello World
;
+ $[0] = t0;
+ } else {
+ t0 = $[0];
+ }
+ return t0;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{}],
+};
+
+```
+
+### Eval output
+(kind: ok) Hello World
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-undefined-initialization-negation.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-undefined-initialization-negation.js
new file mode 100644
index 000000000000..49cb0da33dc4
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-undefined-initialization-negation.js
@@ -0,0 +1,14 @@
+import {useRef} from 'react';
+
+function Component() {
+ const ref = useRef(undefined);
+ if (!ref.current) {
+ ref.current = 'initialized';
+ }
+ return Hello World
;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{}],
+};
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-undefined-initialization.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-undefined-initialization.expect.md
new file mode 100644
index 000000000000..f9f870449bab
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-undefined-initialization.expect.md
@@ -0,0 +1,51 @@
+
+## Input
+
+```javascript
+import {useRef} from 'react';
+
+function Component() {
+ const ref = useRef(undefined);
+ if (ref.current === undefined) {
+ ref.current = "initialized";
+ }
+ return Hello World
;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{}],
+};
+```
+
+## Code
+
+```javascript
+import { c as _c } from "react/compiler-runtime";
+import { useRef } from "react";
+
+function Component() {
+ const $ = _c(1);
+ const ref = useRef(undefined);
+ if (ref.current === undefined) {
+ ref.current = "initialized";
+ }
+ let t0;
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
+ t0 = Hello World
;
+ $[0] = t0;
+ } else {
+ t0 = $[0];
+ }
+ return t0;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{}],
+};
+
+```
+
+### Eval output
+(kind: ok) Hello World
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-undefined-initialization.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-undefined-initialization.js
new file mode 100644
index 000000000000..bf7844ad81be
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-undefined-initialization.js
@@ -0,0 +1,14 @@
+import {useRef} from 'react';
+
+function Component() {
+ const ref = useRef(undefined);
+ if (ref.current === undefined) {
+ ref.current = 'initialized';
+ }
+ return Hello World
;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{}],
+};
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/valid-ref-initialization-unary-not.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/valid-ref-initialization-unary-not.expect.md
new file mode 100644
index 000000000000..4e8d9c4b57a0
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/valid-ref-initialization-unary-not.expect.md
@@ -0,0 +1,42 @@
+
+## Input
+
+```javascript
+//@flow
+import {useRef} from 'react';
+
+component C() {
+ const r = useRef(null);
+ if (!r.current) {
+ r.current = 1;
+ }
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: C,
+ params: [{}],
+};
+
+```
+
+## Code
+
+```javascript
+import { useRef } from "react";
+
+function C() {
+ const r = useRef(null);
+ if (!r.current) {
+ r.current = 1;
+ }
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: C,
+ params: [{}],
+};
+
+```
+
+### Eval output
+(kind: ok)
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-ref-initialization-unary-not.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/valid-ref-initialization-unary-not.js
similarity index 100%
rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-ref-initialization-unary-not.js
rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/valid-ref-initialization-unary-not.js