diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts index bbd076e46a914..90921454c864f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts @@ -356,7 +356,7 @@ function* runWithEnvironment( }); if (env.config.inferEffectDependencies) { - inferEffectDependencies(env, hir); + inferEffectDependencies(hir); } if (env.config.inlineJsxTransform) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index 432eaf96ff99b..90f53d495b5a3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -242,9 +242,40 @@ const EnvironmentConfigSchema = z.object({ enableOptionalDependencies: z.boolean().default(true), /** - * Enables inference and auto-insertion of effect dependencies. Still experimental. + * Enables inference and auto-insertion of effect dependencies. Takes in an array of + * configurable module and import pairs to allow for user-land experimentation. For example, + * [ + * { + * module: 'react', + * imported: 'useEffect', + * numRequiredArgs: 1, + * },{ + * module: 'MyExperimentalEffectHooks', + * imported: 'useExperimentalEffect', + * numRequiredArgs: 2, + * }, + * ] + * would insert dependencies for calls of `useEffect` imported from `react` and calls of + * useExperimentalEffect` from `MyExperimentalEffectHooks`. + * + * `numRequiredArgs` tells the compiler the amount of arguments required to append a dependency + * array to the end of the call. With the configuration above, we'd insert dependencies for + * `useEffect` if it is only given a single argument and it would be appended to the argument list. + * + * numRequiredArgs must always be greater than 0, otherwise there is no function to analyze for dependencies + * + * Still experimental. */ - inferEffectDependencies: z.boolean().default(false), + inferEffectDependencies: z + .nullable( + z.array( + z.object({ + function: ExternalFunctionSchema, + numRequiredArgs: z.number(), + }), + ), + ) + .default(null), /** * Enables inlining ReactElement object literals in place of JSX @@ -614,6 +645,22 @@ const testComplexConfigDefaults: PartialEnvironmentConfig = { source: 'react-compiler-runtime', importSpecifierName: 'useContext_withSelector', }, + inferEffectDependencies: [ + { + function: { + source: 'react', + importSpecifierName: 'useEffect', + }, + numRequiredArgs: 1, + }, + { + function: { + source: 'shared-runtime', + importSpecifierName: 'useSpecialEffect', + }, + numRequiredArgs: 2, + }, + ], }; /** diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts index 9dc7dff78a6af..9fb91a4ac829b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts @@ -8,7 +8,6 @@ import { HIRFunction, IdentifierId, Instruction, - isUseEffectHookType, makeInstructionId, TInstruction, InstructionId, @@ -23,20 +22,33 @@ import { markInstructionIds, } from '../HIR/HIRBuilder'; import {eachInstructionOperand, eachTerminalOperand} from '../HIR/visitors'; +import {getOrInsertWith} from '../Utils/utils'; /** * Infers reactive dependencies captured by useEffect lambdas and adds them as * a second argument to the useEffect call if no dependency array is provided. */ -export function inferEffectDependencies( - env: Environment, - fn: HIRFunction, -): void { +export function inferEffectDependencies(fn: HIRFunction): void { let hasRewrite = false; const fnExpressions = new Map< IdentifierId, TInstruction >(); + + const autodepFnConfigs = new Map>(); + for (const effectTarget of fn.env.config.inferEffectDependencies!) { + const moduleTargets = getOrInsertWith( + autodepFnConfigs, + effectTarget.function.source, + () => new Map(), + ); + moduleTargets.set( + effectTarget.function.importSpecifierName, + effectTarget.numRequiredArgs, + ); + } + const autodepFnLoads = new Map(); + const scopeInfos = new Map< ScopeId, {pruned: boolean; deps: ReactiveScopeDependencies; hasSingleInstr: boolean} @@ -74,15 +86,23 @@ export function inferEffectDependencies( lvalue.identifier.id, instr as TInstruction, ); + } else if ( + value.kind === 'LoadGlobal' && + value.binding.kind === 'ImportSpecifier' + ) { + const moduleTargets = autodepFnConfigs.get(value.binding.module); + if (moduleTargets != null) { + const numRequiredArgs = moduleTargets.get(value.binding.imported); + if (numRequiredArgs != null) { + autodepFnLoads.set(lvalue.identifier.id, numRequiredArgs); + } + } } else if ( /* - * This check is not final. Right now we only look for useEffects without a dependency array. - * This is likely not how we will ship this feature, but it is good enough for us to make progress - * on the implementation and test it. + * TODO: Handle method calls */ value.kind === 'CallExpression' && - isUseEffectHookType(value.callee.identifier) && - value.args.length === 1 && + autodepFnLoads.get(value.callee.identifier.id) === value.args.length && value.args[0].kind === 'Identifier' ) { const fnExpr = fnExpressions.get(value.args[0].identifier.id); @@ -132,7 +152,7 @@ export function inferEffectDependencies( loc: GeneratedSource, }; - const depsPlace = createTemporaryPlace(env, GeneratedSource); + const depsPlace = createTemporaryPlace(fn.env, GeneratedSource); depsPlace.effect = Effect.Read; newInstructions.push({ @@ -142,8 +162,8 @@ export function inferEffectDependencies( value: deps, }); - // Step 2: insert the deps array as an argument of the useEffect - value.args[1] = {...depsPlace, effect: Effect.Freeze}; + // Step 2: push the inferred deps array as an argument of the useEffect + value.args.push({...depsPlace, effect: Effect.Freeze}); rewriteInstrs.set(instr.id, newInstructions); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts index 0202d3ecf043f..2b752c6dfd28e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts @@ -6,7 +6,6 @@ */ import { - ArrayPattern, BlockId, HIRFunction, Identifier, @@ -184,29 +183,28 @@ function rewriteInstruction(instr: Instruction, state: State): void { switch (instr.value.lvalue.pattern.kind) { case 'ArrayPattern': { /* - * For arrays, we can only eliminate unused items from the end of the array, - * so we iterate from the end and break once we find a used item. Note that - * we already know at least one item is used, from the pruneableValue check. + * For arrays, we can prune items prior to the end by replacing + * them with a hole. Items at the end can simply be dropped. */ - let nextItems: ArrayPattern['items'] | null = null; - const originalItems = instr.value.lvalue.pattern.items; - for (let i = originalItems.length - 1; i >= 0; i--) { - const item = originalItems[i]; + let lastEntryIndex = 0; + const items = instr.value.lvalue.pattern.items; + for (let i = 0; i < items.length; i++) { + const item = items[i]; if (item.kind === 'Identifier') { - if (state.isIdOrNameUsed(item.identifier)) { - nextItems = originalItems.slice(0, i + 1); - break; + if (!state.isIdOrNameUsed(item.identifier)) { + items[i] = {kind: 'Hole'}; + } else { + lastEntryIndex = i; } } else if (item.kind === 'Spread') { - if (state.isIdOrNameUsed(item.place.identifier)) { - nextItems = originalItems.slice(0, i + 1); - break; + if (!state.isIdOrNameUsed(item.place.identifier)) { + items[i] = {kind: 'Hole'}; + } else { + lastEntryIndex = i; } } } - if (nextItems !== null) { - instr.value.lvalue.pattern.items = nextItems; - } + items.length = lastEntryIndex + 1; break; } case 'ObjectPattern': { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/concise-arrow-expr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/concise-arrow-expr.expect.md index 787418f75b3fb..996afa1cb224b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/concise-arrow-expr.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/concise-arrow-expr.expect.md @@ -16,7 +16,7 @@ function component() { import { c as _c } from "react/compiler-runtime"; function component() { const $ = _c(1); - const [x, setX] = useState(0); + const [, setX] = useState(0); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { const handler = (v) => setX(v); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/escape-analysis-destructured-rest-element.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/escape-analysis-destructured-rest-element.expect.md index dfa55302aee84..f38ffba958587 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/escape-analysis-destructured-rest-element.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/escape-analysis-destructured-rest-element.expect.md @@ -35,8 +35,7 @@ function Component(props) { } let d; if ($[2] !== props.c) { - const [c, ...t0] = props.c; - d = t0; + [, ...d] = props.c; $[2] = props.c; $[3] = d; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inadvertent-mutability-readonly-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inadvertent-mutability-readonly-lambda.expect.md index 72433173b17ca..76dfc5ab1ca5c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inadvertent-mutability-readonly-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inadvertent-mutability-readonly-lambda.expect.md @@ -24,7 +24,7 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(2); - const [value, setValue] = useState(null); + const [, setValue] = useState(null); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = (e) => setValue((value_0) => value_0 + e.target.value); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-deps-custom-config.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-deps-custom-config.expect.md new file mode 100644 index 0000000000000..b439d5dee07e2 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-deps-custom-config.expect.md @@ -0,0 +1,61 @@ + +## Input + +```javascript +// @inferEffectDependencies +import {print, useSpecialEffect} from 'shared-runtime'; + +function CustomConfig({propVal}) { + // Insertion + useSpecialEffect(() => print(propVal), [propVal]); + // No insertion + useSpecialEffect(() => print(propVal), [propVal], [propVal]); +} + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies +import { print, useSpecialEffect } from "shared-runtime"; + +function CustomConfig(t0) { + const $ = _c(7); + const { propVal } = t0; + let t1; + let t2; + if ($[0] !== propVal) { + t1 = () => print(propVal); + t2 = [propVal]; + $[0] = propVal; + $[1] = t1; + $[2] = t2; + } else { + t1 = $[1]; + t2 = $[2]; + } + useSpecialEffect(t1, t2, [propVal]); + let t3; + let t4; + let t5; + if ($[3] !== propVal) { + t3 = () => print(propVal); + t4 = [propVal]; + t5 = [propVal]; + $[3] = propVal; + $[4] = t3; + $[5] = t4; + $[6] = t5; + } else { + t3 = $[4]; + t4 = $[5]; + t5 = $[6]; + } + useSpecialEffect(t3, t4, t5); +} + +``` + +### Eval output +(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-deps-custom-config.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-deps-custom-config.js new file mode 100644 index 0000000000000..124e607a8ff48 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-deps-custom-config.js @@ -0,0 +1,9 @@ +// @inferEffectDependencies +import {print, useSpecialEffect} from 'shared-runtime'; + +function CustomConfig({propVal}) { + // Insertion + useSpecialEffect(() => print(propVal), [propVal]); + // No insertion + useSpecialEffect(() => print(propVal), [propVal], [propVal]); +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.expect.md index febdd005a3cb0..6e45941f24264 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.expect.md @@ -3,6 +3,8 @@ ```javascript // @inferEffectDependencies +import {useEffect, useRef} from 'react'; + const moduleNonReactive = 0; function Component({foo, bar}) { @@ -45,6 +47,8 @@ function Component({foo, bar}) { ```javascript import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies +import { useEffect, useRef } from "react"; + const moduleNonReactive = 0; function Component(t0) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.js index 6a70bc1298b0a..723ad6516565f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.js @@ -1,4 +1,6 @@ // @inferEffectDependencies +import {useEffect, useRef} from 'react'; + const moduleNonReactive = 0; function Component({foo, bar}) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-calls-to-hoisted-callback-from-other-callback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-calls-to-hoisted-callback-from-other-callback.expect.md index 710f3ac8ec782..57ca944af6bc9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-calls-to-hoisted-callback-from-other-callback.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-calls-to-hoisted-callback-from-other-callback.expect.md @@ -39,7 +39,7 @@ import { useState } from "react"; function Component(props) { const $ = _c(1); - const [_state, setState] = useState(); + const [, setState] = useState(); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { const a = () => b(); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/preserve-use-memo-transition.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/preserve-use-memo-transition.expect.md index 941c37bc3f29f..3687198df3a99 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/preserve-use-memo-transition.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/preserve-use-memo-transition.expect.md @@ -28,7 +28,7 @@ import { useCallback, useTransition } from "react"; function useFoo() { const $ = _c(1); - const [t, start] = useTransition(); + const [, start] = useTransition(); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = () => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md index 0afc5b651bb94..5deb18397d91b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md @@ -32,7 +32,7 @@ function Component(props) { const $ = _c(5); React.useContext(FooContext); const ref = React.useRef(); - const [x, setX] = React.useState(false); + const [, setX] = React.useState(false); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = () => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-control-dependency-phi-setState-type.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-control-dependency-phi-setState-type.expect.md index 26e1f35d5b94a..c56bb7936c1aa 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-control-dependency-phi-setState-type.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-control-dependency-phi-setState-type.expect.md @@ -61,8 +61,8 @@ import { useState } from "react"; function Component(props) { const $ = _c(5); - const [x, setX] = useState(false); - const [y, setY] = useState(false); + const [, setX] = useState(false); + const [, setY] = useState(false); let setState; if (props.cond) { setState = setX; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-undefined-expression-of-jsxexpressioncontainer.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-undefined-expression-of-jsxexpressioncontainer.expect.md index b04c51abc51de..3e71c05a2efbf 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-undefined-expression-of-jsxexpressioncontainer.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-undefined-expression-of-jsxexpressioncontainer.expect.md @@ -52,8 +52,7 @@ function Component(props) { const { buttons } = props; let nonPrimaryButtons; if ($[0] !== buttons) { - const [primaryButton, ...t0] = buttons; - nonPrimaryButtons = t0; + [, ...nonPrimaryButtons] = buttons; $[0] = buttons; $[1] = nonPrimaryButtons; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unused-array-middle-element.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unused-array-middle-element.expect.md index 2cf76a57adf40..4ae1fb6e22c43 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unused-array-middle-element.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unused-array-middle-element.expect.md @@ -19,7 +19,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript function foo(props) { - const [x, unused, y] = props.a; + const [x, , y] = props.a; return x + y; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useActionState-dispatch-considered-as-non-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useActionState-dispatch-considered-as-non-reactive.expect.md index 4181651e3d60f..4b6b94ce431c2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useActionState-dispatch-considered-as-non-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useActionState-dispatch-considered-as-non-reactive.expect.md @@ -29,7 +29,7 @@ import { useActionState } from "react"; function Component() { const $ = _c(1); - const [actionState, dispatchAction] = useActionState(); + const [, dispatchAction] = useActionState(); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { const onSubmitAction = () => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useReducer-returned-dispatcher-is-non-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useReducer-returned-dispatcher-is-non-reactive.expect.md index f5adab196b7bd..a500af2fc8f76 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useReducer-returned-dispatcher-is-non-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useReducer-returned-dispatcher-is-non-reactive.expect.md @@ -30,7 +30,7 @@ import { useReducer } from "react"; function f() { const $ = _c(1); - const [state, dispatch] = useReducer(); + const [, dispatch] = useReducer(); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { const onClick = () => { diff --git a/compiler/packages/snap/src/compiler.ts b/compiler/packages/snap/src/compiler.ts index 6942ffb2adc7b..95af40d62a880 100644 --- a/compiler/packages/snap/src/compiler.ts +++ b/compiler/packages/snap/src/compiler.ts @@ -174,11 +174,6 @@ function makePluginOptions( .filter(s => s.length > 0); } - let inferEffectDependencies = false; - if (firstLine.includes('@inferEffectDependencies')) { - inferEffectDependencies = true; - } - let logs: Array<{filename: string | null; event: LoggerEvent}> = []; let logger: Logger | null = null; if (firstLine.includes('@logger')) { @@ -202,7 +197,6 @@ function makePluginOptions( hookPattern, validatePreserveExistingMemoizationGuarantees, validateBlocklistedImports, - inferEffectDependencies, }, compilationMode, logger, diff --git a/compiler/packages/snap/src/sprout/shared-runtime.ts b/compiler/packages/snap/src/sprout/shared-runtime.ts index bd069ae6d0299..58815842cb03c 100644 --- a/compiler/packages/snap/src/sprout/shared-runtime.ts +++ b/compiler/packages/snap/src/sprout/shared-runtime.ts @@ -363,6 +363,14 @@ export function useFragment(..._args: Array): object { }; } +export function useSpecialEffect( + fn: () => any, + _secondArg: any, + deps: Array, +) { + React.useEffect(fn, deps); +} + export function typedArrayPush(array: Array, item: T): void { array.push(item); } @@ -370,4 +378,5 @@ export function typedArrayPush(array: Array, item: T): void { export function typedLog(...values: Array): void { console.log(...values); } + export default typedLog; diff --git a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js index a0e84b81d034f..a43b6c124df7d 100644 --- a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js +++ b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js @@ -3276,6 +3276,11 @@ describe('ReactHooksWithNoopRenderer', () => { } } + // @gate !enableUseResourceEffectHook + it('is null when flag is disabled', async () => { + expect(useResourceEffect).toBeUndefined(); + }); + // @gate enableUseResourceEffectHook it('validates create return value', async () => { function App({id}) { diff --git a/packages/react/src/ReactClient.js b/packages/react/src/ReactClient.js index 15f70bf70a747..478d9e026aae8 100644 --- a/packages/react/src/ReactClient.js +++ b/packages/react/src/ReactClient.js @@ -65,6 +65,7 @@ import {startTransition} from './ReactStartTransition'; import {act} from './ReactAct'; import {captureOwnerStack} from './ReactOwnerStack'; import ReactCompilerRuntime from './ReactCompilerRuntime'; +import {enableUseResourceEffectHook} from 'shared/ReactFeatureFlags'; const Children = { map, @@ -90,7 +91,6 @@ export { useContext, useEffect, useEffectEvent as experimental_useEffectEvent, - useResourceEffect as experimental_useResourceEffect, useImperativeHandle, useDebugValue, useInsertionEffect, @@ -131,3 +131,6 @@ export { act, // DEV-only captureOwnerStack, // DEV-only }; + +export const experimental_useResourceEffect: typeof useResourceEffect | void = + enableUseResourceEffectHook ? useResourceEffect : undefined;