Skip to content

Commit 21a9523

Browse files
committed
[compiler] Allow functions containing refs to be returned
Summary: We previously were excessively strict about preventing functions that access refs from being returned--doing so is potentially valid for hooks, because the return value may only be used in an event or effect. ghstack-source-id: cfa8bb1 Pull Request resolved: #30724
1 parent 1016174 commit 21a9523

File tree

4 files changed

+90
-57
lines changed

4 files changed

+90
-57
lines changed

compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccesInRender.ts

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -59,20 +59,7 @@ function validateNoRefAccessInRenderImpl(
5959
case 'JsxExpression':
6060
case 'JsxFragment': {
6161
for (const operand of eachInstructionValueOperand(instr.value)) {
62-
if (isRefValueType(operand.identifier)) {
63-
errors.push({
64-
severity: ErrorSeverity.InvalidReact,
65-
reason:
66-
'Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)',
67-
loc: lookupLocations.get(operand.identifier.id) ?? operand.loc,
68-
description:
69-
operand.identifier.name !== null &&
70-
operand.identifier.name.kind === 'named'
71-
? `Cannot access ref value \`${operand.identifier.name.value}\``
72-
: null,
73-
suggestions: null,
74-
});
75-
}
62+
validateNoDirectRefValueAccess(errors, operand, lookupLocations);
7663
}
7764
break;
7865
}
@@ -232,12 +219,17 @@ function validateNoRefAccessInRenderImpl(
232219
}
233220
}
234221
for (const operand of eachTerminalOperand(block.terminal)) {
235-
validateNoRefValueAccess(
236-
errors,
237-
refAccessingFunctions,
238-
lookupLocations,
239-
operand,
240-
);
222+
if (block.terminal.kind !== 'return') {
223+
validateNoRefValueAccess(
224+
errors,
225+
refAccessingFunctions,
226+
lookupLocations,
227+
operand,
228+
);
229+
} else {
230+
// Allow functions containing refs to be returned, but not direct ref values
231+
validateNoDirectRefValueAccess(errors, operand, lookupLocations);
232+
}
241233
}
242234
}
243235

@@ -297,3 +289,24 @@ function validateNoRefAccess(
297289
});
298290
}
299291
}
292+
293+
function validateNoDirectRefValueAccess(
294+
errors: CompilerError,
295+
operand: Place,
296+
lookupLocations: Map<IdentifierId, SourceLocation>,
297+
): void {
298+
if (isRefValueType(operand.identifier)) {
299+
errors.push({
300+
severity: ErrorSeverity.InvalidReact,
301+
reason:
302+
'Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)',
303+
loc: lookupLocations.get(operand.identifier.id) ?? operand.loc,
304+
description:
305+
operand.identifier.name !== null &&
306+
operand.identifier.name.kind === 'named'
307+
? `Cannot access ref value \`${operand.identifier.name.value}\``
308+
: null,
309+
suggestions: null,
310+
});
311+
}
312+
}

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.return-ref-callback.expect.md

Lines changed: 0 additions & 37 deletions
This file was deleted.
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
2+
## Input
3+
4+
```javascript
5+
// @flow @validateRefAccessDuringRender @validatePreserveExistingMemoizationGuarantees
6+
7+
import {useRef} from 'react';
8+
9+
component Foo() {
10+
const ref = useRef();
11+
12+
const s = () => {
13+
return ref.current;
14+
};
15+
16+
return s;
17+
}
18+
19+
export const FIXTURE_ENTRYPOINT = {
20+
fn: Foo,
21+
params: [],
22+
};
23+
24+
```
25+
26+
## Code
27+
28+
```javascript
29+
import { c as _c } from "react/compiler-runtime";
30+
31+
import { useRef } from "react";
32+
33+
function Foo() {
34+
const $ = _c(1);
35+
const ref = useRef();
36+
let t0;
37+
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
38+
t0 = () => ref.current;
39+
$[0] = t0;
40+
} else {
41+
t0 = $[0];
42+
}
43+
const s = t0;
44+
return s;
45+
}
46+
47+
export const FIXTURE_ENTRYPOINT = {
48+
fn: Foo,
49+
params: [],
50+
};
51+
52+
```
53+
54+
### Eval output
55+
(kind: ok) "[[ function params=0 ]]"
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// @flow @validateRefAccessDuringRender @validatePreserveExistingMemoizationGuarantees
22

3+
import {useRef} from 'react';
4+
35
component Foo() {
46
const ref = useRef();
57

0 commit comments

Comments
 (0)