[compiler] Check if local identifier is a hook when resolving globals (#31384)
When resolving import specifiers from the react namespace (`import {imported as local} from 'react'`), we were previously only checking if the `imported` identifier was a hook if we didn't already have its definition in the global registry. We also need to check if `local` is a hook in the case of aliasing since there may be hook-like APIs in react that don't start with `use` (eg they are experimental or unstable). --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31384). * #31385 * __->__ #31384 * #31383
lauren committed
Oct 29, 2024 at 21:36 UTC
4abe4b582106f2012f4eb9624a10f77d167cc848
3 files changed
+185
-1
compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts
+3
-1
@@ -852,7 +852,9 @@ export class Environment {
852
*/
853
return (
854
this.#globals.get(binding.imported) ??
855
- (isHookName(binding.imported) ? this.#getCustomHookType() : null)
855
+ (isHookName(binding.imported) || isHookName(binding.name)
856
+ ? this.#getCustomHookType()
857
+ : null)
858
);
859
} else {
860
const moduleType = this.#resolveModuleType(binding.module, loc);
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/import-as-local.expect.md
new
+140
@@ -0,0 +1,140 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+import {
6
+ useEffect,
7
+ useRef,
8
+ // @ts-expect-error
9
+ experimental_useEffectEvent as useEffectEvent,
10
+} from 'react';
11
+
12
+let id = 0;
13
+function uniqueId() {
14
+ 'use no memo';
15
+ return id++;
16
+}
17
+
18
+export function useCustomHook(src: string): void {
19
+ const uidRef = useRef(uniqueId());
20
+ const destroyed = useRef(false);
21
+ const getItem = (srcName, uid) => {
22
+ return {srcName, uid};
23
+ };
24
+
25
+ const getItemEvent = useEffectEvent(() => {
26
+ if (destroyed.current) return;
27
+
28
+ getItem(src, uidRef.current);
29
+ });
30
+
31
+ useEffect(() => {
32
+ destroyed.current = false;
33
+ getItemEvent();
34
+ }, []);
35
+}
36
+
37
+function Component() {
38
+ useCustomHook('hello');
39
+ return <div>Hello</div>;
40
+}
41
+
42
+export const FIXTURE_ENTRYPOINT = {
43
+ fn: Component,
44
+ isComponent: true,
45
+ params: [{x: 1}],
46
+};
47
+
48
+```
49
+
50
+## Code
51
+
52
+```javascript
53
+import { c as _c } from "react/compiler-runtime";
54
+import {
55
+ useEffect,
56
+ useRef,
57
+ // @ts-expect-error
58
+ experimental_useEffectEvent as useEffectEvent,
59
+} from "react";
60
+
61
+let id = 0;
62
+function uniqueId() {
63
+ "use no memo";
64
+ return id++;
65
+}
66
+
67
+export function useCustomHook(src) {
68
+ const $ = _c(6);
69
+ let t0;
70
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
71
+ t0 = uniqueId();
72
+ $[0] = t0;
73
+ } else {
74
+ t0 = $[0];
75
+ }
76
+ const uidRef = useRef(t0);
77
+ const destroyed = useRef(false);
78
+ const getItem = _temp;
79
+ let t1;
80
+ if ($[1] !== src) {
81
+ t1 = () => {
82
+ if (destroyed.current) {
83
+ return;
84
+ }
85
+
86
+ getItem(src, uidRef.current);
87
+ };
88
+ $[1] = src;
89
+ $[2] = t1;
90
+ } else {
91
+ t1 = $[2];
92
+ }
93
+ const getItemEvent = useEffectEvent(t1);
94
+ let t2;
95
+ if ($[3] !== getItemEvent) {
96
+ t2 = () => {
97
+ destroyed.current = false;
98
+ getItemEvent();
99
+ };
100
+ $[3] = getItemEvent;
101
+ $[4] = t2;
102
+ } else {
103
+ t2 = $[4];
104
+ }
105
+ let t3;
106
+ if ($[5] === Symbol.for("react.memo_cache_sentinel")) {
107
+ t3 = [];
108
+ $[5] = t3;
109
+ } else {
110
+ t3 = $[5];
111
+ }
112
+ useEffect(t2, t3);
113
+}
114
+function _temp(srcName, uid) {
115
+ return { srcName, uid };
116
+}
117
+
118
+function Component() {
119
+ const $ = _c(1);
120
+ useCustomHook("hello");
121
+ let t0;
122
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
123
+ t0 = <div>Hello</div>;
124
+ $[0] = t0;
125
+ } else {
126
+ t0 = $[0];
127
+ }
128
+ return t0;
129
+}
130
+
131
+export const FIXTURE_ENTRYPOINT = {
132
+ fn: Component,
133
+ isComponent: true,
134
+ params: [{ x: 1 }],
135
+};
136
+
137
+```
138
+
139
+### Eval output
140
+(kind: exception) (0 , _react.experimental_useEffectEvent) is not a function
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/import-as-local.tsx
new
+42
@@ -0,0 +1,42 @@
1
+import {
2
+ useEffect,
3
+ useRef,
4
+ // @ts-expect-error
5
+ experimental_useEffectEvent as useEffectEvent,
6
+} from 'react';
7
+
8
+let id = 0;
9
+function uniqueId() {
10
+ 'use no memo';
11
+ return id++;
12
+}
13
+
14
+export function useCustomHook(src: string): void {
15
+ const uidRef = useRef(uniqueId());
16
+ const destroyed = useRef(false);
17
+ const getItem = (srcName, uid) => {
18
+ return {srcName, uid};
19
+ };
20
+
21
+ const getItemEvent = useEffectEvent(() => {
22
+ if (destroyed.current) return;
23
+
24
+ getItem(src, uidRef.current);
25
+ });
26
+
27
+ useEffect(() => {
28
+ destroyed.current = false;
29
+ getItemEvent();
30
+ }, []);
31
+}
32
+
33
+function Component() {
34
+ useCustomHook('hello');
35
+ return <div>Hello</div>;
36
+}
37
+
38
+export const FIXTURE_ENTRYPOINT = {
39
+ fn: Component,
40
+ isComponent: true,
41
+ params: [{x: 1}],
42
+};