@samitouri / QOS-React / commits / 57d5a59748

[compiler] enablePreserveMemo treats manual deps as non-nullable (#34503)

The `@enablePreserveExistingMemoizationGuarantees` mode can still fail to preserve manual memoization due to mismtached dependencies. Specifically, where the user's dependencies are more precise than the compiler infers bc the compiler is being conservative about what might be nullable. In this mode though we're intentionally using information from the manual memoization and can also rely on the deps as a signal for what's non-nullable. The idea of the PR is that we treat manual memo deps just like other inferred-as-non-nullable objects during PropagateScopeDeps. We're careful to not treat the full path as non-nullable, only up to the last property index. So `x.y.z` as a manual dep treats `x` and `x.y` as non-nullable, allowing us to preserve a conditional dependency on `x.y.z`. Optionals within manual dependencies are a bit trickier and aren't handled yet, but hopefully that's less common and something we can improve in a follow-up. Not handling them just means that developers may hit false positives on validating existing memoization if they use optional chains in manual dependencies. --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34503). * #34689 * __->__ #34503

Joseph Savona committed Oct 2, 2025 at 09:48 UTC 57d5a59748bbec1b507bb778c9fbe4bcb82b0a94
9 files changed +624
compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts
+26
@@ -454,6 +454,32 @@ function collectNonNullsInBlocks(
454 assumedNonNullObjects.add(entry);
455 }
456 }
457 + } else if (
458 + fn.env.config.enablePreserveExistingMemoizationGuarantees &&
459 + instr.value.kind === 'StartMemoize' &&
460 + instr.value.deps != null
461 + ) {
462 + for (const dep of instr.value.deps) {
463 + if (dep.root.kind === 'NamedLocal') {
464 + if (
465 + !isImmutableAtInstr(dep.root.value.identifier, instr.id, context)
466 + ) {
467 + continue;
468 + }
469 + for (let i = 0; i < dep.path.length; i++) {
470 + const pathEntry = dep.path[i]!;
471 + if (pathEntry.optional) {
472 + break;
473 + }
474 + const depNode = context.registry.getOrCreateProperty({
475 + identifier: dep.root.value.identifier,
476 + path: dep.path.slice(0, i),
477 + reactive: dep.root.value.reactive,
478 + });
479 + assumedNonNullObjects.add(depNode);
480 + }
481 + }
482 + }
483 }
484 }
485
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-preserve-memo-deps-mixed-optional-nonoptional-property-chain.expect.md new
+91
@@ -0,0 +1,91 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false
6 +
7 +import {useMemo} from 'react';
8 +import {identity, ValidateMemoization} from 'shared-runtime';
9 +
10 +function Component({x}) {
11 + const object = useMemo(() => {
12 + return identity({
13 + callback: () => {
14 + // This is a bug in our dependency inference: we stop capturing dependencies
15 + // after x.a.b?.c. But what this dependency is telling us is that if `x.a.b`
16 + // was non-nullish, then we can access `.c.d?.e`. Thus we should take the
17 + // full property chain, exactly as-is with optionals/non-optionals, as a
18 + // dependency
19 + return identity(x.a.b?.c.d?.e);
20 + },
21 + });
22 + }, [x.a.b?.c.d?.e]);
23 + const result = useMemo(() => {
24 + return [object.callback()];
25 + }, [object]);
26 + return <Inner x={x} result={result} />;
27 +}
28 +
29 +function Inner({x, result}) {
30 + 'use no memo';
31 + return <ValidateMemoization inputs={[x.y.z]} output={result} />;
32 +}
33 +
34 +export const FIXTURE_ENTRYPOINT = {
35 + fn: Component,
36 + params: [{x: {y: {z: 42}}}],
37 + sequentialRenders: [
38 + {x: {y: {z: 42}}},
39 + {x: {y: {z: 42}}},
40 + {x: {y: {z: 3.14}}},
41 + {x: {y: {z: 42}}},
42 + {x: {y: {z: 3.14}}},
43 + {x: {y: {z: 42}}},
44 + ],
45 +};
46 +
47 +```
48 +
49 +
50 +## Error
51 +
52 +```
53 +Found 1 error:
54 +
55 +Compilation Skipped: Existing memoization could not be preserved
56 +
57 +React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `x.a.b?.c`, but the source dependencies were [x.a.b?.c.d?.e]. Inferred less specific property than source.
58 +
59 +error.todo-preserve-memo-deps-mixed-optional-nonoptional-property-chain.ts:7:25
60 + 5 |
61 + 6 | function Component({x}) {
62 +> 7 | const object = useMemo(() => {
63 + | ^^^^^^^
64 +> 8 | return identity({
65 + | ^^^^^^^^^^^^^^^^^^^^^
66 +> 9 | callback: () => {
67 + | ^^^^^^^^^^^^^^^^^^^^^
68 +> 10 | // This is a bug in our dependency inference: we stop capturing dependencies
69 + | ^^^^^^^^^^^^^^^^^^^^^
70 +> 11 | // after x.a.b?.c. But what this dependency is telling us is that if `x.a.b`
71 + | ^^^^^^^^^^^^^^^^^^^^^
72 +> 12 | // was non-nullish, then we can access `.c.d?.e`. Thus we should take the
73 + | ^^^^^^^^^^^^^^^^^^^^^
74 +> 13 | // full property chain, exactly as-is with optionals/non-optionals, as a
75 + | ^^^^^^^^^^^^^^^^^^^^^
76 +> 14 | // dependency
77 + | ^^^^^^^^^^^^^^^^^^^^^
78 +> 15 | return identity(x.a.b?.c.d?.e);
79 + | ^^^^^^^^^^^^^^^^^^^^^
80 +> 16 | },
81 + | ^^^^^^^^^^^^^^^^^^^^^
82 +> 17 | });
83 + | ^^^^^^^^^^^^^^^^^^^^^
84 +> 18 | }, [x.a.b?.c.d?.e]);
85 + | ^^^^ Could not preserve existing manual memoization
86 + 19 | const result = useMemo(() => {
87 + 20 | return [object.callback()];
88 + 21 | }, [object]);
89 +```
90 +
91 +
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-preserve-memo-deps-mixed-optional-nonoptional-property-chain.js new
+41
@@ -0,0 +1,41 @@
1 +// @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false
2 +
3 +import {useMemo} from 'react';
4 +import {identity, ValidateMemoization} from 'shared-runtime';
5 +
6 +function Component({x}) {
7 + const object = useMemo(() => {
8 + return identity({
9 + callback: () => {
10 + // This is a bug in our dependency inference: we stop capturing dependencies
11 + // after x.a.b?.c. But what this dependency is telling us is that if `x.a.b`
12 + // was non-nullish, then we can access `.c.d?.e`. Thus we should take the
13 + // full property chain, exactly as-is with optionals/non-optionals, as a
14 + // dependency
15 + return identity(x.a.b?.c.d?.e);
16 + },
17 + });
18 + }, [x.a.b?.c.d?.e]);
19 + const result = useMemo(() => {
20 + return [object.callback()];
21 + }, [object]);
22 + return <Inner x={x} result={result} />;
23 +}
24 +
25 +function Inner({x, result}) {
26 + 'use no memo';
27 + return <ValidateMemoization inputs={[x.y.z]} output={result} />;
28 +}
29 +
30 +export const FIXTURE_ENTRYPOINT = {
31 + fn: Component,
32 + params: [{x: {y: {z: 42}}}],
33 + sequentialRenders: [
34 + {x: {y: {z: 42}}},
35 + {x: {y: {z: 42}}},
36 + {x: {y: {z: 3.14}}},
37 + {x: {y: {z: 42}}},
38 + {x: {y: {z: 3.14}}},
39 + {x: {y: {z: 42}}},
40 + ],
41 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-conditional-property-chain-less-precise-deps.expect.md new
+122
@@ -0,0 +1,122 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false
6 +
7 +import {useMemo} from 'react';
8 +import {identity, ValidateMemoization} from 'shared-runtime';
9 +
10 +function Component({x}) {
11 + const object = useMemo(() => {
12 + return identity({
13 + callback: () => {
14 + return identity(x.y.z); // accesses more levels of properties than the manual memo
15 + },
16 + });
17 + // x.y as a manual dep only tells us that x is non-nullable, not that x.y is non-nullable
18 + // we can only take a dep on x.y, not x.y.z
19 + }, [x.y]);
20 + const result = useMemo(() => {
21 + return [object.callback()];
22 + }, [object]);
23 + return <ValidateMemoization inputs={[x.y]} output={result} />;
24 +}
25 +
26 +const input1 = {x: {y: {z: 42}}};
27 +const input1b = {x: {y: {z: 42}}};
28 +const input2 = {x: {y: {z: 3.14}}};
29 +export const FIXTURE_ENTRYPOINT = {
30 + fn: Component,
31 + params: [input1],
32 + sequentialRenders: [
33 + input1,
34 + input1,
35 + input1b, // should reset even though .z didn't change
36 + input1,
37 + input2,
38 + ],
39 +};
40 +
41 +```
42 +
43 +## Code
44 +
45 +```javascript
46 +import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false
47 +
48 +import { useMemo } from "react";
49 +import { identity, ValidateMemoization } from "shared-runtime";
50 +
51 +function Component(t0) {
52 + const $ = _c(11);
53 + const { x } = t0;
54 + let t1;
55 + if ($[0] !== x.y) {
56 + t1 = identity({ callback: () => identity(x.y.z) });
57 + $[0] = x.y;
58 + $[1] = t1;
59 + } else {
60 + t1 = $[1];
61 + }
62 + const object = t1;
63 + let t2;
64 + if ($[2] !== object) {
65 + t2 = object.callback();
66 + $[2] = object;
67 + $[3] = t2;
68 + } else {
69 + t2 = $[3];
70 + }
71 + let t3;
72 + if ($[4] !== t2) {
73 + t3 = [t2];
74 + $[4] = t2;
75 + $[5] = t3;
76 + } else {
77 + t3 = $[5];
78 + }
79 + const result = t3;
80 + let t4;
81 + if ($[6] !== x.y) {
82 + t4 = [x.y];
83 + $[6] = x.y;
84 + $[7] = t4;
85 + } else {
86 + t4 = $[7];
87 + }
88 + let t5;
89 + if ($[8] !== result || $[9] !== t4) {
90 + t5 = <ValidateMemoization inputs={t4} output={result} />;
91 + $[8] = result;
92 + $[9] = t4;
93 + $[10] = t5;
94 + } else {
95 + t5 = $[10];
96 + }
97 + return t5;
98 +}
99 +
100 +const input1 = { x: { y: { z: 42 } } };
101 +const input1b = { x: { y: { z: 42 } } };
102 +const input2 = { x: { y: { z: 3.14 } } };
103 +export const FIXTURE_ENTRYPOINT = {
104 + fn: Component,
105 + params: [input1],
106 + sequentialRenders: [
107 + input1,
108 + input1,
109 + input1b, // should reset even though .z didn't change
110 + input1,
111 + input2,
112 + ],
113 +};
114 +
115 +```
116 +
117 +### Eval output
118 +(kind: ok) <div>{"inputs":[{"z":42}],"output":[42]}</div>
119 +<div>{"inputs":[{"z":42}],"output":[42]}</div>
120 +<div>{"inputs":[{"z":42}],"output":[42]}</div>
121 +<div>{"inputs":[{"z":42}],"output":[42]}</div>
122 +<div>{"inputs":[{"z":3.14}],"output":[3.14]}</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-conditional-property-chain-less-precise-deps.js new
+35
@@ -0,0 +1,35 @@
1 +// @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false
2 +
3 +import {useMemo} from 'react';
4 +import {identity, ValidateMemoization} from 'shared-runtime';
5 +
6 +function Component({x}) {
7 + const object = useMemo(() => {
8 + return identity({
9 + callback: () => {
10 + return identity(x.y.z); // accesses more levels of properties than the manual memo
11 + },
12 + });
13 + // x.y as a manual dep only tells us that x is non-nullable, not that x.y is non-nullable
14 + // we can only take a dep on x.y, not x.y.z
15 + }, [x.y]);
16 + const result = useMemo(() => {
17 + return [object.callback()];
18 + }, [object]);
19 + return <ValidateMemoization inputs={[x.y]} output={result} />;
20 +}
21 +
22 +const input1 = {x: {y: {z: 42}}};
23 +const input1b = {x: {y: {z: 42}}};
24 +const input2 = {x: {y: {z: 3.14}}};
25 +export const FIXTURE_ENTRYPOINT = {
26 + fn: Component,
27 + params: [input1],
28 + sequentialRenders: [
29 + input1,
30 + input1,
31 + input1b, // should reset even though .z didn't change
32 + input1,
33 + input2,
34 + ],
35 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-conditional-property-chain.expect.md new
+117
@@ -0,0 +1,117 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false
6 +
7 +import {useMemo} from 'react';
8 +import {identity, ValidateMemoization} from 'shared-runtime';
9 +
10 +function Component({x}) {
11 + const object = useMemo(() => {
12 + return identity({
13 + callback: () => {
14 + return identity(x.y.z);
15 + },
16 + });
17 + }, [x.y.z]);
18 + const result = useMemo(() => {
19 + return [object.callback()];
20 + }, [object]);
21 + return <ValidateMemoization inputs={[x.y.z]} output={result} />;
22 +}
23 +
24 +export const FIXTURE_ENTRYPOINT = {
25 + fn: Component,
26 + params: [{x: {y: {z: 42}}}],
27 + sequentialRenders: [
28 + {x: {y: {z: 42}}},
29 + {x: {y: {z: 42}}},
30 + {x: {y: {z: 3.14}}},
31 + {x: {y: {z: 42}}},
32 + {x: {y: {z: 3.14}}},
33 + {x: {y: {z: 42}}},
34 + ],
35 +};
36 +
37 +```
38 +
39 +## Code
40 +
41 +```javascript
42 +import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false
43 +
44 +import { useMemo } from "react";
45 +import { identity, ValidateMemoization } from "shared-runtime";
46 +
47 +function Component(t0) {
48 + const $ = _c(11);
49 + const { x } = t0;
50 + let t1;
51 + if ($[0] !== x.y.z) {
52 + t1 = identity({ callback: () => identity(x.y.z) });
53 + $[0] = x.y.z;
54 + $[1] = t1;
55 + } else {
56 + t1 = $[1];
57 + }
58 + const object = t1;
59 + let t2;
60 + if ($[2] !== object) {
61 + t2 = object.callback();
62 + $[2] = object;
63 + $[3] = t2;
64 + } else {
65 + t2 = $[3];
66 + }
67 + let t3;
68 + if ($[4] !== t2) {
69 + t3 = [t2];
70 + $[4] = t2;
71 + $[5] = t3;
72 + } else {
73 + t3 = $[5];
74 + }
75 + const result = t3;
76 + let t4;
77 + if ($[6] !== x.y.z) {
78 + t4 = [x.y.z];
79 + $[6] = x.y.z;
80 + $[7] = t4;
81 + } else {
82 + t4 = $[7];
83 + }
84 + let t5;
85 + if ($[8] !== result || $[9] !== t4) {
86 + t5 = <ValidateMemoization inputs={t4} output={result} />;
87 + $[8] = result;
88 + $[9] = t4;
89 + $[10] = t5;
90 + } else {
91 + t5 = $[10];
92 + }
93 + return t5;
94 +}
95 +
96 +export const FIXTURE_ENTRYPOINT = {
97 + fn: Component,
98 + params: [{ x: { y: { z: 42 } } }],
99 + sequentialRenders: [
100 + { x: { y: { z: 42 } } },
101 + { x: { y: { z: 42 } } },
102 + { x: { y: { z: 3.14 } } },
103 + { x: { y: { z: 42 } } },
104 + { x: { y: { z: 3.14 } } },
105 + { x: { y: { z: 42 } } },
106 + ],
107 +};
108 +
109 +```
110 +
111 +### Eval output
112 +(kind: ok) <div>{"inputs":[42],"output":[42]}</div>
113 +<div>{"inputs":[42],"output":[42]}</div>
114 +<div>{"inputs":[3.14],"output":[3.14]}</div>
115 +<div>{"inputs":[42],"output":[42]}</div>
116 +<div>{"inputs":[3.14],"output":[3.14]}</div>
117 +<div>{"inputs":[42],"output":[42]}</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-conditional-property-chain.js new
+31
@@ -0,0 +1,31 @@
1 +// @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false
2 +
3 +import {useMemo} from 'react';
4 +import {identity, ValidateMemoization} from 'shared-runtime';
5 +
6 +function Component({x}) {
7 + const object = useMemo(() => {
8 + return identity({
9 + callback: () => {
10 + return identity(x.y.z);
11 + },
12 + });
13 + }, [x.y.z]);
14 + const result = useMemo(() => {
15 + return [object.callback()];
16 + }, [object]);
17 + return <ValidateMemoization inputs={[x.y.z]} output={result} />;
18 +}
19 +
20 +export const FIXTURE_ENTRYPOINT = {
21 + fn: Component,
22 + params: [{x: {y: {z: 42}}}],
23 + sequentialRenders: [
24 + {x: {y: {z: 42}}},
25 + {x: {y: {z: 42}}},
26 + {x: {y: {z: 3.14}}},
27 + {x: {y: {z: 42}}},
28 + {x: {y: {z: 3.14}}},
29 + {x: {y: {z: 42}}},
30 + ],
31 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-optional-property-chain.expect.md new
+125
@@ -0,0 +1,125 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false
6 +
7 +import {useMemo} from 'react';
8 +import {identity, ValidateMemoization} from 'shared-runtime';
9 +
10 +function Component({x, y, z}) {
11 + const object = useMemo(() => {
12 + return identity({
13 + callback: () => {
14 + return identity(x?.y?.z, y.a?.b, z.a.b?.c);
15 + },
16 + });
17 + }, [x?.y?.z, y.a?.b, z.a.b?.c]);
18 + const result = useMemo(() => {
19 + return [object.callback()];
20 + }, [object]);
21 + return <Inner x={x} result={result} />;
22 +}
23 +
24 +function Inner({x, result}) {
25 + 'use no memo';
26 + return <ValidateMemoization inputs={[x.y.z]} output={result} />;
27 +}
28 +
29 +export const FIXTURE_ENTRYPOINT = {
30 + fn: Component,
31 + params: [{x: {y: {z: 42}}}],
32 + sequentialRenders: [
33 + {x: {y: {z: 42}}},
34 + {x: {y: {z: 42}}},
35 + {x: {y: {z: 3.14}}},
36 + {x: {y: {z: 42}}},
37 + {x: {y: {z: 3.14}}},
38 + {x: {y: {z: 42}}},
39 + ],
40 +};
41 +
42 +```
43 +
44 +## Code
45 +
46 +```javascript
47 +import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false
48 +
49 +import { useMemo } from "react";
50 +import { identity, ValidateMemoization } from "shared-runtime";
51 +
52 +function Component(t0) {
53 + const $ = _c(11);
54 + const { x, y, z } = t0;
55 +
56 + x?.y?.z;
57 + y.a?.b;
58 + z.a.b?.c;
59 + let t1;
60 + if ($[0] !== x?.y?.z || $[1] !== y.a?.b || $[2] !== z.a.b?.c) {
61 + t1 = identity({ callback: () => identity(x?.y?.z, y.a?.b, z.a.b?.c) });
62 + $[0] = x?.y?.z;
63 + $[1] = y.a?.b;
64 + $[2] = z.a.b?.c;
65 + $[3] = t1;
66 + } else {
67 + t1 = $[3];
68 + }
69 + const object = t1;
70 + let t2;
71 + if ($[4] !== object) {
72 + t2 = object.callback();
73 + $[4] = object;
74 + $[5] = t2;
75 + } else {
76 + t2 = $[5];
77 + }
78 + let t3;
79 + if ($[6] !== t2) {
80 + t3 = [t2];
81 + $[6] = t2;
82 + $[7] = t3;
83 + } else {
84 + t3 = $[7];
85 + }
86 + const result = t3;
87 + let t4;
88 + if ($[8] !== result || $[9] !== x) {
89 + t4 = <Inner x={x} result={result} />;
90 + $[8] = result;
91 + $[9] = x;
92 + $[10] = t4;
93 + } else {
94 + t4 = $[10];
95 + }
96 + return t4;
97 +}
98 +
99 +function Inner({ x, result }) {
100 + "use no memo";
101 + return <ValidateMemoization inputs={[x.y.z]} output={result} />;
102 +}
103 +
104 +export const FIXTURE_ENTRYPOINT = {
105 + fn: Component,
106 + params: [{ x: { y: { z: 42 } } }],
107 + sequentialRenders: [
108 + { x: { y: { z: 42 } } },
109 + { x: { y: { z: 42 } } },
110 + { x: { y: { z: 3.14 } } },
111 + { x: { y: { z: 42 } } },
112 + { x: { y: { z: 3.14 } } },
113 + { x: { y: { z: 42 } } },
114 + ],
115 +};
116 +
117 +```
118 +
119 +### Eval output
120 +(kind: ok) [[ (exception in render) TypeError: Cannot read properties of undefined (reading 'a') ]]
121 +[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'a') ]]
122 +[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'a') ]]
123 +[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'a') ]]
124 +[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'a') ]]
125 +[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'a') ]]
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-optional-property-chain.js new
+36
@@ -0,0 +1,36 @@
1 +// @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false
2 +
3 +import {useMemo} from 'react';
4 +import {identity, ValidateMemoization} from 'shared-runtime';
5 +
6 +function Component({x, y, z}) {
7 + const object = useMemo(() => {
8 + return identity({
9 + callback: () => {
10 + return identity(x?.y?.z, y.a?.b, z.a.b?.c);
11 + },
12 + });
13 + }, [x?.y?.z, y.a?.b, z.a.b?.c]);
14 + const result = useMemo(() => {
15 + return [object.callback()];
16 + }, [object]);
17 + return <Inner x={x} result={result} />;
18 +}
19 +
20 +function Inner({x, result}) {
21 + 'use no memo';
22 + return <ValidateMemoization inputs={[x.y.z]} output={result} />;
23 +}
24 +
25 +export const FIXTURE_ENTRYPOINT = {
26 + fn: Component,
27 + params: [{x: {y: {z: 42}}}],
28 + sequentialRenders: [
29 + {x: {y: {z: 42}}},
30 + {x: {y: {z: 42}}},
31 + {x: {y: {z: 3.14}}},
32 + {x: {y: {z: 42}}},
33 + {x: {y: {z: 3.14}}},
34 + {x: {y: {z: 42}}},
35 + ],
36 +};