Ref validation allows passing refs as props
Updates ValidateNoRefAccessInRender to allow passing refs to JSX, but continue disallowing passing ref values (so `ref` is okay but not `ref.current`)
Joe Savona committed
Jun 20, 2023 at 10:11 UTC
4a36b8379611bfaf4bc628eb45542bc3946961cb
5 files changed
+70
-3
compiler/forget/packages/babel-plugin-react-forget/src/HIR/ValidateNoRefAccesInRender.ts
+11
-3
@@ -49,10 +49,18 @@ export function validateNoRefAccessInRender(fn: HIRFunction): void {
49
// for these instructions so they ensure we have a complete analysis.
50
break;
51
}
52
+ case "JsxExpression": {
53
+ // It's okay to pass refs to JSX, but not ref *values*
54
+ for (const operand of eachInstructionValueOperand(instr.value)) {
55
+ validateNonRefValue(error, operand);
56
+ }
57
+ break;
58
+ }
59
case "FunctionExpression": {
53
- // For now we assume *all* function expressions are safe, eventually we can
54
- // be more precise and disallow ref access in functions that may be called
55
- // during render
60
+ // functions are allowed to capture refs, so long as the function is not called
61
+ // during render. see AnalyzeFunctions for how we ensure that functions which
62
+ // capture refs get assigned a mutable range so we know here whether the function
63
+ // is called or not
64
const mutableRange = instr.lvalue.identifier.mutableRange;
65
if (mutableRange.end > mutableRange.start + 1) {
66
for (const operand of eachInstructionValueOperand(instr.value)) {
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/allow-passing-refs-as-props.expect.md
new
+32
@@ -0,0 +1,32 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function Component(props) {
6
+ const ref = useRef(null);
7
+ return <Foo ref={ref} />;
8
+}
9
+
10
+```
11
+
12
+## Code
13
+
14
+```javascript
15
+import { unstable_useMemoCache as useMemoCache } from "react";
16
+function Component(props) {
17
+ const $ = useMemoCache(2);
18
+ const ref = useRef(null);
19
+ const c_0 = $[0] !== ref;
20
+ let t0;
21
+ if (c_0) {
22
+ t0 = <Foo ref={ref} />;
23
+ $[0] = ref;
24
+ $[1] = t0;
25
+ } else {
26
+ t0 = $[1];
27
+ }
28
+ return t0;
29
+}
30
+
31
+```
32
+
\ No newline at end of file
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/allow-passing-refs-as-props.js
new
+4
@@ -0,0 +1,4 @@
1
+function Component(props) {
2
+ const ref = useRef(null);
3
+ return <Foo ref={ref} />;
4
+}
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.invalid-ref-value-as-props.expect.md
new
+19
@@ -0,0 +1,19 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function Component(props) {
6
+ const ref = useRef(null);
7
+ return <Foo ref={ref.current} />;
8
+}
9
+
10
+```
11
+
12
+
13
+## Error
14
+
15
+```
16
+[ReactForget] InvalidInput: Ref values (the `current` property) may not be accessed during render. Cannot access ref value at freeze $20:TObject<BuiltInRefValue> (3:3)
17
+```
18
+
19
+
\ No newline at end of file
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.invalid-ref-value-as-props.js
new
+4
@@ -0,0 +1,4 @@
1
+function Component(props) {
2
+ const ref = useRef(null);
3
+ return <Foo ref={ref.current} />;
4
+}