[compiler] Type `ref` prop as a ref
Adds a shape type for component props, which has one defined property: "ref". This means that if the ref property exists, we can type usage of `props.ref` (or via destructuring) the same as the result of `useRef()` and infer downstream usage similarly. ghstack-source-id: 76cd07c5dfeea2a4aafe141912663b097308fd73 Pull Request resolved: https://github.com/facebook/react/pull/29834
Joe Savona committed
Jun 10, 2024 at 09:52 UTC
c50d593e6390ad200f355ad147dfbc61deaaf8b5
8 files changed
+107
-1
compiler/packages/babel-plugin-react-compiler/src/HIR/ObjectShape.ts
+6
@@ -188,6 +188,7 @@ export type ObjectShape = {
188
* the inferred types for [] and {}.
189
*/
190
export type ShapeRegistry = Map<string, ObjectShape>;
191
+export const BuiltInPropsId = "BuiltInProps";
192
export const BuiltInArrayId = "BuiltInArray";
193
export const BuiltInFunctionId = "BuiltInFunction";
194
export const BuiltInJsxId = "BuiltInJsx";
@@ -207,6 +208,11 @@ export const BuiltInDispatchId = "BuiltInDispatch";
208
// ShapeRegistry with default definitions for built-ins.
209
export const BUILTIN_SHAPES: ShapeRegistry = new Map();
210
211
+// If the `ref` prop exists, it has the ref type
212
+addObject(BUILTIN_SHAPES, BuiltInPropsId, [
213
+ ["ref", { kind: "Object", shapeId: BuiltInUseRefId }],
214
+]);
215
+
216
/* Built-in array shape */
217
addObject(BUILTIN_SHAPES, BuiltInArrayId, [
218
[
compiler/packages/babel-plugin-react-compiler/src/TypeInference/InferTypes.ts
+8
-1
@@ -23,6 +23,7 @@ import {
23
BuiltInFunctionId,
24
BuiltInJsxId,
25
BuiltInObjectId,
26
+ BuiltInPropsId,
27
BuiltInUseRefId,
28
} from "../HIR/ObjectShape";
29
import { eachInstructionLValue, eachInstructionOperand } from "../HIR/visitors";
@@ -101,7 +102,13 @@ function* generate(
102
func: HIRFunction
103
): Generator<TypeEquation, void, undefined> {
104
if (func.env.fnType === "Component") {
104
- const [_, ref] = func.params;
105
+ const [props, ref] = func.params;
106
+ if (props && props.kind === "Identifier") {
107
+ yield equation(props.identifier.type, {
108
+ kind: "Object",
109
+ shapeId: BuiltInPropsId,
110
+ });
111
+ }
112
if (ref && ref.kind === "Identifier") {
113
yield equation(ref.identifier.type, {
114
kind: "Object",
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-destructure.expect.md
new
+25
@@ -0,0 +1,25 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// @validateRefAccessDuringRender @compilationMode(infer)
6
+function Component({ ref }) {
7
+ const value = ref.current;
8
+ return <div>{value}</div>;
9
+}
10
+
11
+```
12
+
13
+
14
+## Error
15
+
16
+```
17
+ 2 | function Component({ ref }) {
18
+ 3 | const value = ref.current;
19
+> 4 | return <div>{value}</div>;
20
+ | ^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef). Cannot access ref value at read $17:TObject<BuiltInRefValue> (4:4)
21
+ 5 | }
22
+ 6 |
23
+```
24
+
25
+
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-destructure.js
new
+5
@@ -0,0 +1,5 @@
1
+// @validateRefAccessDuringRender @compilationMode(infer)
2
+function Component({ ref }) {
3
+ const value = ref.current;
4
+ return <div>{value}</div>;
5
+}
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-property-load.expect.md
new
+25
@@ -0,0 +1,25 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// @validateRefAccessDuringRender @compilationMode(infer)
6
+function Component(props) {
7
+ const value = props.ref.current;
8
+ return <div>{value}</div>;
9
+}
10
+
11
+```
12
+
13
+
14
+## Error
15
+
16
+```
17
+ 2 | function Component(props) {
18
+ 3 | const value = props.ref.current;
19
+> 4 | return <div>{value}</div>;
20
+ | ^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef). Cannot access ref value at read $15:TObject<BuiltInRefValue> (4:4)
21
+ 5 | }
22
+ 6 |
23
+```
24
+
25
+
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-property-load.js
new
+5
@@ -0,0 +1,5 @@
1
+// @validateRefAccessDuringRender @compilationMode(infer)
2
+function Component(props) {
3
+ const value = props.ref.current;
4
+ return <div>{value}</div>;
5
+}
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-write-ref-prop-in-render.expect.md
new
+27
@@ -0,0 +1,27 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// @validateRefAccessDuringRender @compilationMode(infer)
6
+function Component(props) {
7
+ const ref = props.ref;
8
+ ref.current = true;
9
+ return <div>{value}</div>;
10
+}
11
+
12
+```
13
+
14
+
15
+## Error
16
+
17
+```
18
+ 2 | function Component(props) {
19
+ 3 | const ref = props.ref;
20
+> 4 | ref.current = true;
21
+ | ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (4:4)
22
+ 5 | return <div>{value}</div>;
23
+ 6 | }
24
+ 7 |
25
+```
26
+
27
+
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-write-ref-prop-in-render.js
new
+6
@@ -0,0 +1,6 @@
1
+// @validateRefAccessDuringRender @compilationMode(infer)
2
+function Component(props) {
3
+ const ref = props.ref;
4
+ ref.current = true;
5
+ return <div>{value}</div>;
6
+}