@samitouri / QOS-React-1 / commits / 0972e23908

[compiler] Consider setter from useOptimistic non-reactive (#35141)

Closes https://github.com/facebook/react/issues/35138

Ricky committed Nov 18, 2025 at 10:50 UTC 0972e23908b573faa5beebc0eb1154f792c53b62
5 files changed +176 -2
compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts
+13
@@ -23,6 +23,7 @@ import {
23 BuiltInUseInsertionEffectHookId,
24 BuiltInUseLayoutEffectHookId,
25 BuiltInUseOperatorId,
26 + BuiltInUseOptimisticId,
27 BuiltInUseReducerId,
28 BuiltInUseRefId,
29 BuiltInUseStateId,
@@ -818,6 +819,18 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
819 returnValueKind: ValueKind.Frozen,
820 }),
821 ],
822 + [
823 + 'useOptimistic',
824 + addHook(DEFAULT_SHAPES, {
825 + positionalParams: [],
826 + restParam: Effect.Freeze,
827 + returnType: {kind: 'Object', shapeId: BuiltInUseOptimisticId},
828 + calleeEffect: Effect.Read,
829 + hookKind: 'useOptimistic',
830 + returnValueKind: ValueKind.Frozen,
831 + returnValueReason: ValueReason.State,
832 + }),
833 + ],
834 [
835 'use',
836 addFunction(
compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts
+17 -2
@@ -1887,6 +1887,18 @@ export function isStartTransitionType(id: Identifier): boolean {
1887 );
1888 }
1889
1890 +export function isUseOptimisticType(id: Identifier): boolean {
1891 + return (
1892 + id.type.kind === 'Object' && id.type.shapeId === 'BuiltInUseOptimistic'
1893 + );
1894 +}
1895 +
1896 +export function isSetOptimisticType(id: Identifier): boolean {
1897 + return (
1898 + id.type.kind === 'Function' && id.type.shapeId === 'BuiltInSetOptimistic'
1899 + );
1900 +}
1901 +
1902 export function isSetActionStateType(id: Identifier): boolean {
1903 return (
1904 id.type.kind === 'Function' && id.type.shapeId === 'BuiltInSetActionState'
@@ -1920,7 +1932,8 @@ export function isStableType(id: Identifier): boolean {
1932 isSetActionStateType(id) ||
1933 isDispatcherType(id) ||
1934 isUseRefType(id) ||
1923 - isStartTransitionType(id)
1935 + isStartTransitionType(id) ||
1936 + isSetOptimisticType(id)
1937 );
1938 }
1939
@@ -1931,8 +1944,9 @@ export function isStableTypeContainer(id: Identifier): boolean {
1944 }
1945 return (
1946 isUseStateType(id) || // setState
1934 - type_.shapeId === 'BuiltInUseActionState' || // setActionState
1947 + isUseActionStateType(id) || // setActionState
1948 isUseReducerType(id) || // dispatcher
1949 + isUseOptimisticType(id) || // setOptimistic
1950 type_.shapeId === 'BuiltInUseTransition' // startTransition
1951 );
1952 }
@@ -1952,6 +1966,7 @@ export function evaluatesToStableTypeOrContainer(
1966 case 'useActionState':
1967 case 'useRef':
1968 case 'useTransition':
1969 + case 'useOptimistic':
1970 return true;
1971 }
1972 }
compiler/packages/babel-plugin-react-compiler/src/HIR/ObjectShape.ts
+22
@@ -304,6 +304,7 @@ export type HookKind =
304 | 'useTransition'
305 | 'useImperativeHandle'
306 | 'useEffectEvent'
307 + | 'useOptimistic'
308 | 'Custom';
309
310 /*
@@ -399,6 +400,8 @@ export const BuiltInUseReducerId = 'BuiltInUseReducer';
400 export const BuiltInDispatchId = 'BuiltInDispatch';
401 export const BuiltInUseContextHookId = 'BuiltInUseContextHook';
402 export const BuiltInUseTransitionId = 'BuiltInUseTransition';
403 +export const BuiltInUseOptimisticId = 'BuiltInUseOptimistic';
404 +export const BuiltInSetOptimisticId = 'BuiltInSetOptimistic';
405 export const BuiltInStartTransitionId = 'BuiltInStartTransition';
406 export const BuiltInFireId = 'BuiltInFire';
407 export const BuiltInFireFunctionId = 'BuiltInFireFunction';
@@ -1186,6 +1189,25 @@ addObject(BUILTIN_SHAPES, BuiltInUseTransitionId, [
1189 ],
1190 ]);
1191
1192 +addObject(BUILTIN_SHAPES, BuiltInUseOptimisticId, [
1193 + ['0', {kind: 'Poly'}],
1194 + [
1195 + '1',
1196 + addFunction(
1197 + BUILTIN_SHAPES,
1198 + [],
1199 + {
1200 + positionalParams: [],
1201 + restParam: Effect.Freeze,
1202 + returnType: PRIMITIVE_TYPE,
1203 + calleeEffect: Effect.Read,
1204 + returnValueKind: ValueKind.Primitive,
1205 + },
1206 + BuiltInSetOptimisticId,
1207 + ),
1208 + ],
1209 +]);
1210 +
1211 addObject(BUILTIN_SHAPES, BuiltInUseActionStateId, [
1212 ['0', {kind: 'Poly'}],
1213 [
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/preserve-use-callback-stable-built-ins.expect.md new
+91
@@ -0,0 +1,91 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @validatePreserveExistingMemoizationGuarantees
6 +import {
7 + useCallback,
8 + useTransition,
9 + useState,
10 + useOptimistic,
11 + useActionState,
12 + useRef,
13 + useReducer,
14 +} from 'react';
15 +
16 +function useFoo() {
17 + const [s, setState] = useState();
18 + const ref = useRef(null);
19 + const [t, startTransition] = useTransition();
20 + const [u, addOptimistic] = useOptimistic();
21 + const [v, dispatch] = useReducer(() => {}, null);
22 + const [isPending, dispatchAction] = useActionState(() => {}, null);
23 +
24 + return useCallback(() => {
25 + dispatch();
26 + startTransition(() => {});
27 + addOptimistic();
28 + setState(null);
29 + dispatchAction();
30 + ref.current = true;
31 + }, []);
32 +}
33 +
34 +export const FIXTURE_ENTRYPOINT = {
35 + fn: useFoo,
36 + params: [],
37 +};
38 +
39 +```
40 +
41 +## Code
42 +
43 +```javascript
44 +import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees
45 +import {
46 + useCallback,
47 + useTransition,
48 + useState,
49 + useOptimistic,
50 + useActionState,
51 + useRef,
52 + useReducer,
53 +} from "react";
54 +
55 +function useFoo() {
56 + const $ = _c(1);
57 + const [, setState] = useState();
58 + const ref = useRef(null);
59 + const [, startTransition] = useTransition();
60 + const [, addOptimistic] = useOptimistic();
61 + const [, dispatch] = useReducer(_temp, null);
62 + const [, dispatchAction] = useActionState(_temp2, null);
63 + let t0;
64 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
65 + t0 = () => {
66 + dispatch();
67 + startTransition(_temp3);
68 + addOptimistic();
69 + setState(null);
70 + dispatchAction();
71 + ref.current = true;
72 + };
73 + $[0] = t0;
74 + } else {
75 + t0 = $[0];
76 + }
77 + return t0;
78 +}
79 +function _temp3() {}
80 +function _temp2() {}
81 +function _temp() {}
82 +
83 +export const FIXTURE_ENTRYPOINT = {
84 + fn: useFoo,
85 + params: [],
86 +};
87 +
88 +```
89 +
90 +### Eval output
91 +(kind: ok) "[[ function params=0 ]]"
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/preserve-use-callback-stable-built-ins.ts new
+33
@@ -0,0 +1,33 @@
1 +// @validatePreserveExistingMemoizationGuarantees
2 +import {
3 + useCallback,
4 + useTransition,
5 + useState,
6 + useOptimistic,
7 + useActionState,
8 + useRef,
9 + useReducer,
10 +} from 'react';
11 +
12 +function useFoo() {
13 + const [s, setState] = useState();
14 + const ref = useRef(null);
15 + const [t, startTransition] = useTransition();
16 + const [u, addOptimistic] = useOptimistic();
17 + const [v, dispatch] = useReducer(() => {}, null);
18 + const [isPending, dispatchAction] = useActionState(() => {}, null);
19 +
20 + return useCallback(() => {
21 + dispatch();
22 + startTransition(() => {});
23 + addOptimistic();
24 + setState(null);
25 + dispatchAction();
26 + ref.current = true;
27 + }, []);
28 +}
29 +
30 +export const FIXTURE_ENTRYPOINT = {
31 + fn: useFoo,
32 + params: [],
33 +};