diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts index f42d6fc5bd9..441b5d5452a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts @@ -23,6 +23,7 @@ import { BuiltInUseInsertionEffectHookId, BuiltInUseLayoutEffectHookId, BuiltInUseOperatorId, + BuiltInUseOptimisticId, BuiltInUseReducerId, BuiltInUseRefId, BuiltInUseStateId, @@ -818,6 +819,18 @@ const REACT_APIS: Array<[string, BuiltInType]> = [ returnValueKind: ValueKind.Frozen, }), ], + [ + 'useOptimistic', + addHook(DEFAULT_SHAPES, { + positionalParams: [], + restParam: Effect.Freeze, + returnType: {kind: 'Object', shapeId: BuiltInUseOptimisticId}, + calleeEffect: Effect.Read, + hookKind: 'useOptimistic', + returnValueKind: ValueKind.Frozen, + returnValueReason: ValueReason.State, + }), + ], [ 'use', addFunction( diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts index 41e957a5467..5a19490cb6c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -1887,6 +1887,18 @@ export function isStartTransitionType(id: Identifier): boolean { ); } +export function isUseOptimisticType(id: Identifier): boolean { + return ( + id.type.kind === 'Object' && id.type.shapeId === 'BuiltInUseOptimistic' + ); +} + +export function isSetOptimisticType(id: Identifier): boolean { + return ( + id.type.kind === 'Function' && id.type.shapeId === 'BuiltInSetOptimistic' + ); +} + export function isSetActionStateType(id: Identifier): boolean { return ( id.type.kind === 'Function' && id.type.shapeId === 'BuiltInSetActionState' @@ -1920,7 +1932,8 @@ export function isStableType(id: Identifier): boolean { isSetActionStateType(id) || isDispatcherType(id) || isUseRefType(id) || - isStartTransitionType(id) + isStartTransitionType(id) || + isSetOptimisticType(id) ); } @@ -1931,8 +1944,9 @@ export function isStableTypeContainer(id: Identifier): boolean { } return ( isUseStateType(id) || // setState - type_.shapeId === 'BuiltInUseActionState' || // setActionState + isUseActionStateType(id) || // setActionState isUseReducerType(id) || // dispatcher + isUseOptimisticType(id) || // setOptimistic type_.shapeId === 'BuiltInUseTransition' // startTransition ); } @@ -1952,6 +1966,7 @@ export function evaluatesToStableTypeOrContainer( case 'useActionState': case 'useRef': case 'useTransition': + case 'useOptimistic': return true; } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/ObjectShape.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/ObjectShape.ts index eb771615619..c92f9e55623 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/ObjectShape.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/ObjectShape.ts @@ -304,6 +304,7 @@ export type HookKind = | 'useTransition' | 'useImperativeHandle' | 'useEffectEvent' + | 'useOptimistic' | 'Custom'; /* @@ -399,6 +400,8 @@ export const BuiltInUseReducerId = 'BuiltInUseReducer'; export const BuiltInDispatchId = 'BuiltInDispatch'; export const BuiltInUseContextHookId = 'BuiltInUseContextHook'; export const BuiltInUseTransitionId = 'BuiltInUseTransition'; +export const BuiltInUseOptimisticId = 'BuiltInUseOptimistic'; +export const BuiltInSetOptimisticId = 'BuiltInSetOptimistic'; export const BuiltInStartTransitionId = 'BuiltInStartTransition'; export const BuiltInFireId = 'BuiltInFire'; export const BuiltInFireFunctionId = 'BuiltInFireFunction'; @@ -1186,6 +1189,25 @@ addObject(BUILTIN_SHAPES, BuiltInUseTransitionId, [ ], ]); +addObject(BUILTIN_SHAPES, BuiltInUseOptimisticId, [ + ['0', {kind: 'Poly'}], + [ + '1', + addFunction( + BUILTIN_SHAPES, + [], + { + positionalParams: [], + restParam: Effect.Freeze, + returnType: PRIMITIVE_TYPE, + calleeEffect: Effect.Read, + returnValueKind: ValueKind.Primitive, + }, + BuiltInSetOptimisticId, + ), + ], +]); + addObject(BUILTIN_SHAPES, BuiltInUseActionStateId, [ ['0', {kind: 'Poly'}], [ diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/preserve-use-callback-stable-built-ins.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/preserve-use-callback-stable-built-ins.expect.md new file mode 100644 index 00000000000..be4f25b64c4 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/preserve-use-callback-stable-built-ins.expect.md @@ -0,0 +1,91 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees +import { + useCallback, + useTransition, + useState, + useOptimistic, + useActionState, + useRef, + useReducer, +} from 'react'; + +function useFoo() { + const [s, setState] = useState(); + const ref = useRef(null); + const [t, startTransition] = useTransition(); + const [u, addOptimistic] = useOptimistic(); + const [v, dispatch] = useReducer(() => {}, null); + const [isPending, dispatchAction] = useActionState(() => {}, null); + + return useCallback(() => { + dispatch(); + startTransition(() => {}); + addOptimistic(); + setState(null); + dispatchAction(); + ref.current = true; + }, []); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees +import { + useCallback, + useTransition, + useState, + useOptimistic, + useActionState, + useRef, + useReducer, +} from "react"; + +function useFoo() { + const $ = _c(1); + const [, setState] = useState(); + const ref = useRef(null); + const [, startTransition] = useTransition(); + const [, addOptimistic] = useOptimistic(); + const [, dispatch] = useReducer(_temp, null); + const [, dispatchAction] = useActionState(_temp2, null); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = () => { + dispatch(); + startTransition(_temp3); + addOptimistic(); + setState(null); + dispatchAction(); + ref.current = true; + }; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} +function _temp3() {} +function _temp2() {} +function _temp() {} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; + +``` + +### Eval output +(kind: ok) "[[ function params=0 ]]" \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/preserve-use-callback-stable-built-ins.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/preserve-use-callback-stable-built-ins.ts new file mode 100644 index 00000000000..b8120de3c3c --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/preserve-use-callback-stable-built-ins.ts @@ -0,0 +1,33 @@ +// @validatePreserveExistingMemoizationGuarantees +import { + useCallback, + useTransition, + useState, + useOptimistic, + useActionState, + useRef, + useReducer, +} from 'react'; + +function useFoo() { + const [s, setState] = useState(); + const ref = useRef(null); + const [t, startTransition] = useTransition(); + const [u, addOptimistic] = useOptimistic(); + const [v, dispatch] = useReducer(() => {}, null); + const [isPending, dispatchAction] = useActionState(() => {}, null); + + return useCallback(() => { + dispatch(); + startTransition(() => {}); + addOptimistic(); + setState(null); + dispatchAction(); + ref.current = true; + }, []); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +};