@samitouri / QOS-React-1 / commits / 85923690e9

compiler: Improve ValidateNoRefAccessInRender to ignore access in effects

Improves ValidateNoRefAccessInRender (still disabled by default) to properly ignore ref access within effects. This includes allowing ref access within functions that are only transitively called from an effect. While I was here I also added some extra test fixtures for allowing global mutation in effects. ghstack-source-id: fb6352a1788b7bdbebb40d5b844b711ef87d6771 Pull Request resolved: https://github.com/facebook/react/pull/29151

Joe Savona committed May 17, 2024 at 14:35 UTC 85923690e96ed5c03a419550d5db9c035e864f2d
13 files changed +774 -15
compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccesInRender.ts
+26 -15
@@ -19,6 +19,7 @@ import {
19 eachTerminalOperand,
20 } from "../HIR/visitors";
21 import { Err, Ok, Result } from "../Utils/Result";
22 +import { isEffectHook } from "./ValidateMemoizedEffectDependencies";
23
24 /**
25 * Validates that a function does not access a ref value during render. This includes a partial check
@@ -113,27 +114,37 @@ function validateNoRefAccessInRenderImpl(
114 }
115 break;
116 }
117 + case "MethodCall": {
118 + if (!isEffectHook(instr.value.property.identifier)) {
119 + for (const operand of eachInstructionValueOperand(instr.value)) {
120 + validateNoRefAccess(errors, refAccessingFunctions, operand);
121 + }
122 + }
123 + break;
124 + }
125 case "CallExpression": {
126 const callee = instr.value.callee;
118 - // Report a more precise error when calling a local function that accesses a ref
119 - if (refAccessingFunctions.has(callee.identifier.id)) {
120 - errors.push({
121 - severity: ErrorSeverity.InvalidReact,
122 - reason:
123 - "This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef)",
124 - loc: callee.loc,
125 - description: `Function ${printPlace(callee)} accesses a ref`,
126 - suggestions: null,
127 - });
128 - }
129 - for (const operand of eachInstructionValueOperand(instr.value)) {
130 - validateNoRefAccess(errors, refAccessingFunctions, operand);
127 + const isUseEffect = isEffectHook(callee.identifier);
128 + if (!isUseEffect) {
129 + // Report a more precise error when calling a local function that accesses a ref
130 + if (refAccessingFunctions.has(callee.identifier.id)) {
131 + errors.push({
132 + severity: ErrorSeverity.InvalidReact,
133 + reason:
134 + "This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef)",
135 + loc: callee.loc,
136 + description: `Function ${printPlace(callee)} accesses a ref`,
137 + suggestions: null,
138 + });
139 + }
140 + for (const operand of eachInstructionValueOperand(instr.value)) {
141 + validateNoRefAccess(errors, refAccessingFunctions, operand);
142 + }
143 }
144 break;
145 }
146 case "ObjectExpression":
135 - case "ArrayExpression":
136 - case "MethodCall": {
147 + case "ArrayExpression": {
148 for (const operand of eachInstructionValueOperand(instr.value)) {
149 validateNoRefAccess(errors, refAccessingFunctions, operand);
150 }
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-global-mutation-in-effect-indirect-usecallback.expect.md new
+106
@@ -0,0 +1,106 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +import { useCallback, useEffect, useState } from "react";
6 +
7 +let someGlobal = {};
8 +
9 +function Component() {
10 + const [state, setState] = useState(someGlobal);
11 +
12 + const setGlobal = useCallback(() => {
13 + someGlobal.value = true;
14 + }, []);
15 + useEffect(() => {
16 + setGlobal();
17 + }, []);
18 +
19 + useEffect(() => {
20 + setState(someGlobal.value);
21 + }, [someGlobal]);
22 +
23 + return <div>{String(state)}</div>;
24 +}
25 +
26 +export const FIXTURE_ENTRYPOINT = {
27 + fn: Component,
28 + params: [{}],
29 +};
30 +
31 +```
32 +
33 +## Code
34 +
35 +```javascript
36 +import { c as _c } from "react/compiler-runtime";
37 +import { useCallback, useEffect, useState } from "react";
38 +
39 +let someGlobal = {};
40 +
41 +function Component() {
42 + const $ = _c(7);
43 + const [state, setState] = useState(someGlobal);
44 + let t0;
45 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
46 + t0 = () => {
47 + someGlobal.value = true;
48 + };
49 + $[0] = t0;
50 + } else {
51 + t0 = $[0];
52 + }
53 + const setGlobal = t0;
54 + let t1;
55 + let t2;
56 + if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
57 + t1 = () => {
58 + setGlobal();
59 + };
60 + t2 = [];
61 + $[1] = t1;
62 + $[2] = t2;
63 + } else {
64 + t1 = $[1];
65 + t2 = $[2];
66 + }
67 + useEffect(t1, t2);
68 + let t3;
69 + if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
70 + t3 = () => {
71 + setState(someGlobal.value);
72 + };
73 + $[3] = t3;
74 + } else {
75 + t3 = $[3];
76 + }
77 + let t4;
78 + if ($[4] === Symbol.for("react.memo_cache_sentinel")) {
79 + t4 = [someGlobal];
80 + $[4] = t4;
81 + } else {
82 + t4 = $[4];
83 + }
84 + useEffect(t3, t4);
85 +
86 + const t5 = String(state);
87 + let t6;
88 + if ($[5] !== t5) {
89 + t6 = <div>{t5}</div>;
90 + $[5] = t5;
91 + $[6] = t6;
92 + } else {
93 + t6 = $[6];
94 + }
95 + return t6;
96 +}
97 +
98 +export const FIXTURE_ENTRYPOINT = {
99 + fn: Component,
100 + params: [{}],
101 +};
102 +
103 +```
104 +
105 +### Eval output
106 +(kind: ok) <div>true</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-global-mutation-in-effect-indirect-usecallback.js new
+25
@@ -0,0 +1,25 @@
1 +import { useCallback, useEffect, useState } from "react";
2 +
3 +let someGlobal = {};
4 +
5 +function Component() {
6 + const [state, setState] = useState(someGlobal);
7 +
8 + const setGlobal = useCallback(() => {
9 + someGlobal.value = true;
10 + }, []);
11 + useEffect(() => {
12 + setGlobal();
13 + }, []);
14 +
15 + useEffect(() => {
16 + setState(someGlobal.value);
17 + }, [someGlobal]);
18 +
19 + return <div>{String(state)}</div>;
20 +}
21 +
22 +export const FIXTURE_ENTRYPOINT = {
23 + fn: Component,
24 + params: [{}],
25 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-global-mutation-in-effect-indirect.expect.md new
+106
@@ -0,0 +1,106 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +import { useEffect, useState } from "react";
6 +
7 +let someGlobal = {};
8 +
9 +function Component() {
10 + const [state, setState] = useState(someGlobal);
11 +
12 + const setGlobal = () => {
13 + someGlobal.value = true;
14 + };
15 + useEffect(() => {
16 + setGlobal();
17 + }, []);
18 +
19 + useEffect(() => {
20 + setState(someGlobal.value);
21 + }, [someGlobal]);
22 +
23 + return <div>{String(state)}</div>;
24 +}
25 +
26 +export const FIXTURE_ENTRYPOINT = {
27 + fn: Component,
28 + params: [{}],
29 +};
30 +
31 +```
32 +
33 +## Code
34 +
35 +```javascript
36 +import { c as _c } from "react/compiler-runtime";
37 +import { useEffect, useState } from "react";
38 +
39 +let someGlobal = {};
40 +
41 +function Component() {
42 + const $ = _c(7);
43 + const [state, setState] = useState(someGlobal);
44 + let t0;
45 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
46 + t0 = () => {
47 + someGlobal.value = true;
48 + };
49 + $[0] = t0;
50 + } else {
51 + t0 = $[0];
52 + }
53 + const setGlobal = t0;
54 + let t1;
55 + let t2;
56 + if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
57 + t1 = () => {
58 + setGlobal();
59 + };
60 + t2 = [];
61 + $[1] = t1;
62 + $[2] = t2;
63 + } else {
64 + t1 = $[1];
65 + t2 = $[2];
66 + }
67 + useEffect(t1, t2);
68 + let t3;
69 + if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
70 + t3 = () => {
71 + setState(someGlobal.value);
72 + };
73 + $[3] = t3;
74 + } else {
75 + t3 = $[3];
76 + }
77 + let t4;
78 + if ($[4] === Symbol.for("react.memo_cache_sentinel")) {
79 + t4 = [someGlobal];
80 + $[4] = t4;
81 + } else {
82 + t4 = $[4];
83 + }
84 + useEffect(t3, t4);
85 +
86 + const t5 = String(state);
87 + let t6;
88 + if ($[5] !== t5) {
89 + t6 = <div>{t5}</div>;
90 + $[5] = t5;
91 + $[6] = t6;
92 + } else {
93 + t6 = $[6];
94 + }
95 + return t6;
96 +}
97 +
98 +export const FIXTURE_ENTRYPOINT = {
99 + fn: Component,
100 + params: [{}],
101 +};
102 +
103 +```
104 +
105 +### Eval output
106 +(kind: ok) <div>true</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-global-mutation-in-effect-indirect.js new
+25
@@ -0,0 +1,25 @@
1 +import { useEffect, useState } from "react";
2 +
3 +let someGlobal = {};
4 +
5 +function Component() {
6 + const [state, setState] = useState(someGlobal);
7 +
8 + const setGlobal = () => {
9 + someGlobal.value = true;
10 + };
11 + useEffect(() => {
12 + setGlobal();
13 + }, []);
14 +
15 + useEffect(() => {
16 + setState(someGlobal.value);
17 + }, [someGlobal]);
18 +
19 + return <div>{String(state)}</div>;
20 +}
21 +
22 +export const FIXTURE_ENTRYPOINT = {
23 + fn: Component,
24 + params: [{}],
25 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-global-mutation-unused-usecallback.expect.md new
+48
@@ -0,0 +1,48 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +import { useCallback, useEffect, useState } from "react";
6 +
7 +function Component() {
8 + const callback = useCallback(() => {
9 + window.foo = true;
10 + }, []);
11 +
12 + return <div>Ok</div>;
13 +}
14 +
15 +export const FIXTURE_ENTRYPOINT = {
16 + fn: Component,
17 + params: [{}],
18 +};
19 +
20 +```
21 +
22 +## Code
23 +
24 +```javascript
25 +import { c as _c } from "react/compiler-runtime";
26 +import { useCallback, useEffect, useState } from "react";
27 +
28 +function Component() {
29 + const $ = _c(1);
30 + let t0;
31 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
32 + t0 = <div>Ok</div>;
33 + $[0] = t0;
34 + } else {
35 + t0 = $[0];
36 + }
37 + return t0;
38 +}
39 +
40 +export const FIXTURE_ENTRYPOINT = {
41 + fn: Component,
42 + params: [{}],
43 +};
44 +
45 +```
46 +
47 +### Eval output
48 +(kind: ok) <div>Ok</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-global-mutation-unused-usecallback.js new
+14
@@ -0,0 +1,14 @@
1 +import { useCallback, useEffect, useState } from "react";
2 +
3 +function Component() {
4 + const callback = useCallback(() => {
5 + window.foo = true;
6 + }, []);
7 +
8 + return <div>Ok</div>;
9 +}
10 +
11 +export const FIXTURE_ENTRYPOINT = {
12 + fn: Component,
13 + params: [{}],
14 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-effect-indirect.expect.md new
+121
@@ -0,0 +1,121 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @validateRefAccessDuringRender
6 +import { useCallback, useEffect, useRef, useState } from "react";
7 +
8 +function Component() {
9 + const ref = useRef(null);
10 + const [state, setState] = useState(false);
11 + const setRef = useCallback(() => {
12 + ref.current = "Ok";
13 + }, []);
14 +
15 + useEffect(() => {
16 + setRef();
17 + }, []);
18 +
19 + useEffect(() => {
20 + setState(true);
21 + }, []);
22 +
23 + // We use state to force a re-render and observe whether the
24 + // ref updated. This lets us check that the effect actually ran
25 + // and wasn't DCE'd
26 + return <Child key={String(state)} ref={ref} />;
27 +}
28 +
29 +function Child({ ref }) {
30 + // This violates the rules of React, so we access the ref in a child
31 + // component
32 + return ref.current;
33 +}
34 +
35 +export const FIXTURE_ENTRYPOINT = {
36 + fn: Component,
37 + params: [{}],
38 +};
39 +
40 +```
41 +
42 +## Code
43 +
44 +```javascript
45 +import { c as _c } from "react/compiler-runtime"; // @validateRefAccessDuringRender
46 +import { useCallback, useEffect, useRef, useState } from "react";
47 +
48 +function Component() {
49 + const $ = _c(9);
50 + const ref = useRef(null);
51 + const [state, setState] = useState(false);
52 + let t0;
53 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
54 + t0 = () => {
55 + ref.current = "Ok";
56 + };
57 + $[0] = t0;
58 + } else {
59 + t0 = $[0];
60 + }
61 + const setRef = t0;
62 + let t1;
63 + if ($[1] !== setRef) {
64 + t1 = () => {
65 + setRef();
66 + };
67 + $[1] = setRef;
68 + $[2] = t1;
69 + } else {
70 + t1 = $[2];
71 + }
72 + let t2;
73 + if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
74 + t2 = [];
75 + $[3] = t2;
76 + } else {
77 + t2 = $[3];
78 + }
79 + useEffect(t1, t2);
80 + let t3;
81 + let t4;
82 + if ($[4] === Symbol.for("react.memo_cache_sentinel")) {
83 + t3 = () => {
84 + setState(true);
85 + };
86 + t4 = [];
87 + $[4] = t3;
88 + $[5] = t4;
89 + } else {
90 + t3 = $[4];
91 + t4 = $[5];
92 + }
93 + useEffect(t3, t4);
94 +
95 + const t5 = String(state);
96 + let t6;
97 + if ($[6] !== t5 || $[7] !== ref) {
98 + t6 = <Child key={t5} ref={ref} />;
99 + $[6] = t5;
100 + $[7] = ref;
101 + $[8] = t6;
102 + } else {
103 + t6 = $[8];
104 + }
105 + return t6;
106 +}
107 +
108 +function Child(t0) {
109 + const { ref } = t0;
110 + return ref.current;
111 +}
112 +
113 +export const FIXTURE_ENTRYPOINT = {
114 + fn: Component,
115 + params: [{}],
116 +};
117 +
118 +```
119 +
120 +### Eval output
121 +(kind: ok) Ok
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-effect-indirect.js new
+34
@@ -0,0 +1,34 @@
1 +// @validateRefAccessDuringRender
2 +import { useCallback, useEffect, useRef, useState } from "react";
3 +
4 +function Component() {
5 + const ref = useRef(null);
6 + const [state, setState] = useState(false);
7 + const setRef = useCallback(() => {
8 + ref.current = "Ok";
9 + }, []);
10 +
11 + useEffect(() => {
12 + setRef();
13 + }, []);
14 +
15 + useEffect(() => {
16 + setState(true);
17 + }, []);
18 +
19 + // We use state to force a re-render and observe whether the
20 + // ref updated. This lets us check that the effect actually ran
21 + // and wasn't DCE'd
22 + return <Child key={String(state)} ref={ref} />;
23 +}
24 +
25 +function Child({ ref }) {
26 + // This violates the rules of React, so we access the ref in a child
27 + // component
28 + return ref.current;
29 +}
30 +
31 +export const FIXTURE_ENTRYPOINT = {
32 + fn: Component,
33 + params: [{}],
34 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-effect.expect.md new
+103
@@ -0,0 +1,103 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @validateRefAccessDuringRender
6 +import { useEffect, useRef, useState } from "react";
7 +
8 +function Component() {
9 + const ref = useRef(null);
10 + const [state, setState] = useState(false);
11 + useEffect(() => {
12 + ref.current = "Ok";
13 + }, []);
14 +
15 + useEffect(() => {
16 + setState(true);
17 + }, []);
18 +
19 + // We use state to force a re-render and observe whether the
20 + // ref updated. This lets us check that the effect actually ran
21 + // and wasn't DCE'd
22 + return <Child key={String(state)} ref={ref} />;
23 +}
24 +
25 +function Child({ ref }) {
26 + // This violates the rules of React, so we access the ref in a child
27 + // component
28 + return ref.current;
29 +}
30 +
31 +export const FIXTURE_ENTRYPOINT = {
32 + fn: Component,
33 + params: [{}],
34 +};
35 +
36 +```
37 +
38 +## Code
39 +
40 +```javascript
41 +import { c as _c } from "react/compiler-runtime"; // @validateRefAccessDuringRender
42 +import { useEffect, useRef, useState } from "react";
43 +
44 +function Component() {
45 + const $ = _c(7);
46 + const ref = useRef(null);
47 + const [state, setState] = useState(false);
48 + let t0;
49 + let t1;
50 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
51 + t0 = () => {
52 + ref.current = "Ok";
53 + };
54 + t1 = [];
55 + $[0] = t0;
56 + $[1] = t1;
57 + } else {
58 + t0 = $[0];
59 + t1 = $[1];
60 + }
61 + useEffect(t0, t1);
62 + let t2;
63 + let t3;
64 + if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
65 + t2 = () => {
66 + setState(true);
67 + };
68 + t3 = [];
69 + $[2] = t2;
70 + $[3] = t3;
71 + } else {
72 + t2 = $[2];
73 + t3 = $[3];
74 + }
75 + useEffect(t2, t3);
76 +
77 + const t4 = String(state);
78 + let t5;
79 + if ($[4] !== t4 || $[5] !== ref) {
80 + t5 = <Child key={t4} ref={ref} />;
81 + $[4] = t4;
82 + $[5] = ref;
83 + $[6] = t5;
84 + } else {
85 + t5 = $[6];
86 + }
87 + return t5;
88 +}
89 +
90 +function Child(t0) {
91 + const { ref } = t0;
92 + return ref.current;
93 +}
94 +
95 +export const FIXTURE_ENTRYPOINT = {
96 + fn: Component,
97 + params: [{}],
98 +};
99 +
100 +```
101 +
102 +### Eval output
103 +(kind: ok) Ok
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-effect.js new
+30
@@ -0,0 +1,30 @@
1 +// @validateRefAccessDuringRender
2 +import { useEffect, useRef, useState } from "react";
3 +
4 +function Component() {
5 + const ref = useRef(null);
6 + const [state, setState] = useState(false);
7 + useEffect(() => {
8 + ref.current = "Ok";
9 + }, []);
10 +
11 + useEffect(() => {
12 + setState(true);
13 + }, []);
14 +
15 + // We use state to force a re-render and observe whether the
16 + // ref updated. This lets us check that the effect actually ran
17 + // and wasn't DCE'd
18 + return <Child key={String(state)} ref={ref} />;
19 +}
20 +
21 +function Child({ ref }) {
22 + // This violates the rules of React, so we access the ref in a child
23 + // component
24 + return ref.current;
25 +}
26 +
27 +export const FIXTURE_ENTRYPOINT = {
28 + fn: Component,
29 + params: [{}],
30 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md new
+104
@@ -0,0 +1,104 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @validateRefAccessDuringRender
6 +import { useEffect, useRef, useState } from "react";
7 +
8 +function Component() {
9 + const ref = useRef(null);
10 + const [state, setState] = useState(false);
11 + useEffect(() => {
12 + const callback = () => {
13 + ref.current = "Ok";
14 + };
15 + }, []);
16 +
17 + useEffect(() => {
18 + setState(true);
19 + }, []);
20 +
21 + // We use state to force a re-render and observe whether the
22 + // ref updated. This lets us check that the effect actually ran
23 + // and wasn't DCE'd
24 + return <Child key={String(state)} ref={ref} />;
25 +}
26 +
27 +function Child({ ref }) {
28 + // This violates the rules of React, so we access the ref in a child
29 + // component
30 + return ref.current;
31 +}
32 +
33 +export const FIXTURE_ENTRYPOINT = {
34 + fn: Component,
35 + params: [{}],
36 +};
37 +
38 +```
39 +
40 +## Code
41 +
42 +```javascript
43 +import { c as _c } from "react/compiler-runtime"; // @validateRefAccessDuringRender
44 +import { useEffect, useRef, useState } from "react";
45 +
46 +function Component() {
47 + const $ = _c(7);
48 + const ref = useRef(null);
49 + const [state, setState] = useState(false);
50 + let t0;
51 + let t1;
52 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
53 + t0 = () => {};
54 +
55 + t1 = [];
56 + $[0] = t0;
57 + $[1] = t1;
58 + } else {
59 + t0 = $[0];
60 + t1 = $[1];
61 + }
62 + useEffect(t0, t1);
63 + let t2;
64 + let t3;
65 + if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
66 + t2 = () => {
67 + setState(true);
68 + };
69 + t3 = [];
70 + $[2] = t2;
71 + $[3] = t3;
72 + } else {
73 + t2 = $[2];
74 + t3 = $[3];
75 + }
76 + useEffect(t2, t3);
77 +
78 + const t4 = String(state);
79 + let t5;
80 + if ($[4] !== t4 || $[5] !== ref) {
81 + t5 = <Child key={t4} ref={ref} />;
82 + $[4] = t4;
83 + $[5] = ref;
84 + $[6] = t5;
85 + } else {
86 + t5 = $[6];
87 + }
88 + return t5;
89 +}
90 +
91 +function Child(t0) {
92 + const { ref } = t0;
93 + return ref.current;
94 +}
95 +
96 +export const FIXTURE_ENTRYPOINT = {
97 + fn: Component,
98 + params: [{}],
99 +};
100 +
101 +```
102 +
103 +### Eval output
104 +(kind: ok)
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.js new
+32
@@ -0,0 +1,32 @@
1 +// @validateRefAccessDuringRender
2 +import { useEffect, useRef, useState } from "react";
3 +
4 +function Component() {
5 + const ref = useRef(null);
6 + const [state, setState] = useState(false);
7 + useEffect(() => {
8 + const callback = () => {
9 + ref.current = "Ok";
10 + };
11 + }, []);
12 +
13 + useEffect(() => {
14 + setState(true);
15 + }, []);
16 +
17 + // We use state to force a re-render and observe whether the
18 + // ref updated. This lets us check that the effect actually ran
19 + // and wasn't DCE'd
20 + return <Child key={String(state)} ref={ref} />;
21 +}
22 +
23 +function Child({ ref }) {
24 + // This violates the rules of React, so we access the ref in a child
25 + // component
26 + return ref.current;
27 +}
28 +
29 +export const FIXTURE_ENTRYPOINT = {
30 + fn: Component,
31 + params: [{}],
32 +};