diff --git a/packages/react-debug-tools/src/ReactDebugHooks.js b/packages/react-debug-tools/src/ReactDebugHooks.js index 2a0c57154e6f9..eccaa6a67b166 100644 --- a/packages/react-debug-tools/src/ReactDebugHooks.js +++ b/packages/react-debug-tools/src/ReactDebugHooks.js @@ -104,22 +104,14 @@ function getPrimitiveStackCache(): Map> { ); Dispatcher.useDeferredValue(null); Dispatcher.useMemo(() => null); + Dispatcher.useOptimistic(null, (s: mixed, a: mixed) => s); + Dispatcher.useFormState((s: mixed, p: mixed) => s, null); + Dispatcher.useActionState((s: mixed, p: mixed) => s, null); + Dispatcher.useHostTransitionStatus(); if (typeof Dispatcher.useMemoCache === 'function') { // This type check is for Flow only. Dispatcher.useMemoCache(0); } - if (typeof Dispatcher.useOptimistic === 'function') { - // This type check is for Flow only. - Dispatcher.useOptimistic(null, (s: mixed, a: mixed) => s); - } - if (typeof Dispatcher.useFormState === 'function') { - // This type check is for Flow only. - Dispatcher.useFormState((s: mixed, p: mixed) => s, null); - } - if (typeof Dispatcher.useActionState === 'function') { - // This type check is for Flow only. - Dispatcher.useActionState((s: mixed, p: mixed) => s, null); - } if (typeof Dispatcher.use === 'function') { // This type check is for Flow only. Dispatcher.use( @@ -143,11 +135,6 @@ function getPrimitiveStackCache(): Map> { } Dispatcher.useId(); - - if (typeof Dispatcher.useHostTransitionStatus === 'function') { - // This type check is for Flow only. - Dispatcher.useHostTransitionStatus(); - } } finally { readHookLog = hookLog; hookLog = []; diff --git a/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js b/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js index ef9a7c1e6edc7..fcd84fedfed71 100644 --- a/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js +++ b/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js @@ -2581,7 +2581,6 @@ describe('ReactHooksInspectionIntegration', () => { `); }); - // @gate enableAsyncActions it('should support useOptimistic hook', async () => { const useOptimistic = React.useOptimistic; function Foo() { @@ -2647,7 +2646,6 @@ describe('ReactHooksInspectionIntegration', () => { `); }); - // @gate enableAsyncActions it('should support useActionState hook', async () => { function Foo() { const [value] = React.useActionState(function increment(n) { diff --git a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js index 0933794c68cb6..8e94d48beeef2 100644 --- a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js +++ b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js @@ -91,7 +91,6 @@ import { enableCreateEventHandleAPI, enableScopeAPI, enableTrustedTypesIntegration, - enableAsyncActions, disableLegacyMode, enableMoveBefore, } from 'shared/ReactFeatureFlags'; @@ -1378,9 +1377,8 @@ function getNextHydratable(node: ?Node) { nodeData === SUSPENSE_START_DATA || nodeData === SUSPENSE_FALLBACK_START_DATA || nodeData === SUSPENSE_PENDING_START_DATA || - (enableAsyncActions && - (nodeData === FORM_STATE_IS_MATCHING || - nodeData === FORM_STATE_IS_NOT_MATCHING)) + nodeData === FORM_STATE_IS_MATCHING || + nodeData === FORM_STATE_IS_NOT_MATCHING ) { break; } diff --git a/packages/react-dom-bindings/src/shared/ReactDOMFormActions.js b/packages/react-dom-bindings/src/shared/ReactDOMFormActions.js index ab36fc11199b1..6dd4e4da44479 100644 --- a/packages/react-dom-bindings/src/shared/ReactDOMFormActions.js +++ b/packages/react-dom-bindings/src/shared/ReactDOMFormActions.js @@ -10,7 +10,6 @@ import type {Dispatcher} from 'react-reconciler/src/ReactInternalTypes'; import type {Awaited} from 'shared/ReactTypes'; -import {enableAsyncActions} from 'shared/ReactFeatureFlags'; import ReactSharedInternals from 'shared/ReactSharedInternals'; import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals'; @@ -66,13 +65,8 @@ function resolveDispatcher() { } export function useFormStatus(): FormStatus { - if (!enableAsyncActions) { - throw new Error('Not implemented.'); - } else { - const dispatcher = resolveDispatcher(); - // $FlowFixMe[not-a-function] We know this exists because of the feature check above. - return dispatcher.useHostTransitionStatus(); - } + const dispatcher = resolveDispatcher(); + return dispatcher.useHostTransitionStatus(); } export function useFormState( @@ -80,13 +74,8 @@ export function useFormState( initialState: Awaited, permalink?: string, ): [Awaited, (P) => void, boolean] { - if (!enableAsyncActions) { - throw new Error('Not implemented.'); - } else { - const dispatcher = resolveDispatcher(); - // $FlowFixMe[not-a-function] This is unstable, thus optional - return dispatcher.useFormState(action, initialState, permalink); - } + const dispatcher = resolveDispatcher(); + return dispatcher.useFormState(action, initialState, permalink); } export function requestFormReset(form: HTMLFormElement) { diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzForm-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzForm-test.js index 0ecfe3eb82e0b..2300f4563c4b6 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFizzForm-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFizzForm-test.js @@ -398,7 +398,6 @@ describe('ReactDOMFizzForm', () => { expect(buttonRef.current.hasAttribute('formTarget')).toBe(false); }); - // @gate enableAsyncActions it('useFormStatus is not pending during server render', async () => { function App() { const {pending} = useFormStatus(); @@ -488,7 +487,6 @@ describe('ReactDOMFizzForm', () => { expect(rootActionCalled).toBe(false); }); - // @gate enableAsyncActions it('useOptimistic returns passthrough value', async () => { function App() { const [optimisticState] = useOptimistic('hi'); @@ -507,7 +505,6 @@ describe('ReactDOMFizzForm', () => { expect(container.textContent).toBe('hi'); }); - // @gate enableAsyncActions it('useActionState returns initial state', async () => { async function action(state) { return state; diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js index 8a8619ff87e7a..9c91be1225015 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js @@ -6330,7 +6330,6 @@ describe('ReactDOMFizzServer', () => { expect(getVisibleChildren(container)).toEqual('Hi'); }); - // @gate enableAsyncActions it('useActionState hydrates without a mismatch', async () => { // This is testing an implementation detail: useActionState emits comment // nodes into the SSR stream, so this checks that they are handled correctly @@ -6383,7 +6382,6 @@ describe('ReactDOMFizzServer', () => { expect(childRef.current).toBe(child); }); - // @gate enableAsyncActions it("useActionState hydrates without a mismatch if there's a render phase update", async () => { async function action(state) { return state; diff --git a/packages/react-dom/src/__tests__/ReactDOMForm-test.js b/packages/react-dom/src/__tests__/ReactDOMForm-test.js index 168d44722d473..7185cc5cad152 100644 --- a/packages/react-dom/src/__tests__/ReactDOMForm-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMForm-test.js @@ -669,7 +669,6 @@ describe('ReactDOMForm', () => { expect(actionCalled).toBe(false); }); - // @gate enableAsyncActions it('form actions are transitions', async () => { const formRef = React.createRef(); @@ -707,7 +706,6 @@ describe('ReactDOMForm', () => { expect(container.textContent).toBe('Updated'); }); - // @gate enableAsyncActions it('multiple form actions', async () => { const formRef = React.createRef(); @@ -798,12 +796,6 @@ describe('ReactDOMForm', () => { }); it('sync errors in form actions can be captured by an error boundary', async () => { - if (gate(flags => !flags.enableAsyncActions)) { - // TODO: Uncaught JSDOM errors fail the test after the scope has finished - // so don't work with the `gate` mechanism. - return; - } - class ErrorBoundary extends React.Component { state = {error: null}; static getDerivedStateFromError(error) { @@ -844,12 +836,6 @@ describe('ReactDOMForm', () => { }); it('async errors in form actions can be captured by an error boundary', async () => { - if (gate(flags => !flags.enableAsyncActions)) { - // TODO: Uncaught JSDOM errors fail the test after the scope has finished - // so don't work with the `gate` mechanism. - return; - } - class ErrorBoundary extends React.Component { state = {error: null}; static getDerivedStateFromError(error) { @@ -895,7 +881,6 @@ describe('ReactDOMForm', () => { expect(container.textContent).toBe('Oh no!'); }); - // @gate enableAsyncActions it('useFormStatus reads the status of a pending form action', async () => { const formRef = React.createRef(); @@ -992,7 +977,6 @@ describe('ReactDOMForm', () => { ); }); - // @gate enableAsyncActions it('useActionState updates state asynchronously and queues multiple actions', async () => { let actionCounter = 0; async function action(state, type) { @@ -1052,7 +1036,6 @@ describe('ReactDOMForm', () => { expect(container.textContent).toBe('2'); }); - // @gate enableAsyncActions it('useActionState supports inline actions', async () => { let increment; function App({stepSize}) { @@ -1084,7 +1067,6 @@ describe('ReactDOMForm', () => { assertLog(['Pending 1', '11']); }); - // @gate enableAsyncActions it('useActionState: dispatch throws if called during render', async () => { function App() { const [state, dispatch, isPending] = useActionState(async () => {}, 0); @@ -1100,7 +1082,6 @@ describe('ReactDOMForm', () => { }); }); - // @gate enableAsyncActions it('useActionState: queues multiple actions and runs them in order', async () => { let action; function App() { @@ -1132,7 +1113,6 @@ describe('ReactDOMForm', () => { expect(container.textContent).toBe('D'); }); - // @gate enableAsyncActions it( 'useActionState: when calling a queued action, uses the implementation ' + 'that was current at the time it was dispatched, not the most recent one', @@ -1179,7 +1159,6 @@ describe('ReactDOMForm', () => { }, ); - // @gate enableAsyncActions it('useActionState: works if action is sync', async () => { let increment; function App({stepSize}) { @@ -1211,7 +1190,6 @@ describe('ReactDOMForm', () => { assertLog(['Pending 1', '11']); }); - // @gate enableAsyncActions it('useActionState: can mix sync and async actions', async () => { let action; function App() { @@ -1239,7 +1217,6 @@ describe('ReactDOMForm', () => { expect(container.textContent).toBe('E'); }); - // @gate enableAsyncActions it('useActionState: error handling (sync action)', async () => { class ErrorBoundary extends React.Component { state = {error: null}; @@ -1288,7 +1265,6 @@ describe('ReactDOMForm', () => { expect(container.textContent).toBe('Caught an error: Oops!'); }); - // @gate enableAsyncActions it('useActionState: error handling (async action)', async () => { class ErrorBoundary extends React.Component { state = {error: null}; @@ -1394,7 +1370,6 @@ describe('ReactDOMForm', () => { expect(container.textContent).toBe('Caught an error: Oops!'); }); - // @gate enableAsyncActions it('useActionState works in StrictMode', async () => { let actionCounter = 0; async function action(state, type) { diff --git a/packages/react-dom/src/client/ReactDOMRoot.js b/packages/react-dom/src/client/ReactDOMRoot.js index 98ec6f9236fed..1c673bf2543a0 100644 --- a/packages/react-dom/src/client/ReactDOMRoot.js +++ b/packages/react-dom/src/client/ReactDOMRoot.js @@ -16,7 +16,6 @@ import type { import {isValidContainer} from 'react-dom-bindings/src/client/ReactDOMContainer'; import {queueExplicitHydrationTarget} from 'react-dom-bindings/src/events/ReactDOMEventReplaying'; import {REACT_ELEMENT_TYPE} from 'shared/ReactSymbols'; -import {enableAsyncActions} from 'shared/ReactFeatureFlags'; export type RootType = { render(children: ReactNodeList): void, @@ -305,10 +304,8 @@ export function hydrateRoot( if (options.unstable_transitionCallbacks !== undefined) { transitionCallbacks = options.unstable_transitionCallbacks; } - if (enableAsyncActions) { - if (options.formState !== undefined) { - formState = options.formState; - } + if (options.formState !== undefined) { + formState = options.formState; } } diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index df222199b2012..119129d506c09 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -106,7 +106,6 @@ import { enableTransitionTracing, enableLegacyHidden, enableCPUSuspense, - enableAsyncActions, enablePostpone, enableRenderableContext, disableLegacyMode, @@ -1619,55 +1618,53 @@ function updateHostComponent( workInProgress.flags |= ContentReset; } - if (enableAsyncActions) { - const memoizedState = workInProgress.memoizedState; - if (memoizedState !== null) { - // This fiber has been upgraded to a stateful component. The only way - // happens currently is for form actions. We use hooks to track the - // pending and error state of the form. - // - // Once a fiber is upgraded to be stateful, it remains stateful for the - // rest of its lifetime. - const newState = renderTransitionAwareHostComponentWithHooks( - current, - workInProgress, - renderLanes, - ); + const memoizedState = workInProgress.memoizedState; + if (memoizedState !== null) { + // This fiber has been upgraded to a stateful component. The only way + // happens currently is for form actions. We use hooks to track the + // pending and error state of the form. + // + // Once a fiber is upgraded to be stateful, it remains stateful for the + // rest of its lifetime. + const newState = renderTransitionAwareHostComponentWithHooks( + current, + workInProgress, + renderLanes, + ); - // If the transition state changed, propagate the change to all the - // descendents. We use Context as an implementation detail for this. - // - // This is intentionally set here instead of pushHostContext because - // pushHostContext gets called before we process the state hook, to avoid - // a state mismatch in the event that something suspends. - // - // NOTE: This assumes that there cannot be nested transition providers, - // because the only renderer that implements this feature is React DOM, - // and forms cannot be nested. If we did support nested providers, then - // we would need to push a context value even for host fibers that - // haven't been upgraded yet. - if (isPrimaryRenderer) { - HostTransitionContext._currentValue = newState; - } else { - HostTransitionContext._currentValue2 = newState; - } - if (enableLazyContextPropagation) { - // In the lazy propagation implementation, we don't scan for matching - // consumers until something bails out. - } else { - if (didReceiveUpdate) { - if (current !== null) { - const oldStateHook: Hook = current.memoizedState; - const oldState: TransitionStatus = oldStateHook.memoizedState; - // This uses regular equality instead of Object.is because we assume - // that host transition state doesn't include NaN as a valid type. - if (oldState !== newState) { - propagateContextChange( - workInProgress, - HostTransitionContext, - renderLanes, - ); - } + // If the transition state changed, propagate the change to all the + // descendents. We use Context as an implementation detail for this. + // + // This is intentionally set here instead of pushHostContext because + // pushHostContext gets called before we process the state hook, to avoid + // a state mismatch in the event that something suspends. + // + // NOTE: This assumes that there cannot be nested transition providers, + // because the only renderer that implements this feature is React DOM, + // and forms cannot be nested. If we did support nested providers, then + // we would need to push a context value even for host fibers that + // haven't been upgraded yet. + if (isPrimaryRenderer) { + HostTransitionContext._currentValue = newState; + } else { + HostTransitionContext._currentValue2 = newState; + } + if (enableLazyContextPropagation) { + // In the lazy propagation implementation, we don't scan for matching + // consumers until something bails out. + } else { + if (didReceiveUpdate) { + if (current !== null) { + const oldStateHook: Hook = current.memoizedState; + const oldState: TransitionStatus = oldStateHook.memoizedState; + // This uses regular equality instead of Object.is because we assume + // that host transition state doesn't include NaN as a valid type. + if (oldState !== newState) { + propagateContextChange( + workInProgress, + HostTransitionContext, + renderLanes, + ); } } } diff --git a/packages/react-reconciler/src/ReactFiberHooks.js b/packages/react-reconciler/src/ReactFiberHooks.js index 514f93760981c..033e71610f5c6 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.js +++ b/packages/react-reconciler/src/ReactFiberHooks.js @@ -44,7 +44,6 @@ import { enableUseEffectEventHook, enableLegacyCache, debugRenderPhaseSideEffectsForStrictMode, - enableAsyncActions, disableLegacyMode, enableNoCloningMemoCache, enableContextProfiling, @@ -907,9 +906,6 @@ export function renderTransitionAwareHostComponentWithHooks( workInProgress: Fiber, lanes: Lanes, ): TransitionStatus { - if (!enableAsyncActions) { - throw new Error('Not implemented.'); - } return renderWithHooks( current, workInProgress, @@ -921,10 +917,6 @@ export function renderTransitionAwareHostComponentWithHooks( } export function TransitionAwareHostComponent(): TransitionStatus { - if (!enableAsyncActions) { - throw new Error('Not implemented.'); - } - const dispatcher: any = ReactSharedInternals.H; const [maybeThenable] = dispatcher.useState(); let nextState; @@ -1490,7 +1482,7 @@ function updateReducerImpl( // Check if this is an optimistic update. const revertLane = update.revertLane; - if (!enableAsyncActions || revertLane === NoLane) { + if (revertLane === NoLane) { // This is not an optimistic update, and we're going to apply it now. // But, if there were earlier updates that were skipped, we need to // leave this update in the queue so it can be rebased later. @@ -3268,25 +3260,14 @@ function startTransition( const prevTransition = ReactSharedInternals.T; const currentTransition: BatchConfigTransition = {}; - if (enableAsyncActions) { - // We don't really need to use an optimistic update here, because we - // schedule a second "revert" update below (which we use to suspend the - // transition until the async action scope has finished). But we'll use an - // optimistic update anyway to make it less likely the behavior accidentally - // diverges; for example, both an optimistic update and this one should - // share the same lane. - ReactSharedInternals.T = currentTransition; - dispatchOptimisticSetState(fiber, false, queue, pendingState); - } else { - ReactSharedInternals.T = null; - dispatchSetStateInternal( - fiber, - queue, - pendingState, - requestUpdateLane(fiber), - ); - ReactSharedInternals.T = currentTransition; - } + // We don't really need to use an optimistic update here, because we + // schedule a second "revert" update below (which we use to suspend the + // transition until the async action scope has finished). But we'll use an + // optimistic update anyway to make it less likely the behavior accidentally + // diverges; for example, both an optimistic update and this one should + // share the same lane. + ReactSharedInternals.T = currentTransition; + dispatchOptimisticSetState(fiber, false, queue, pendingState); if (enableTransitionTracing) { if (options !== undefined && options.name !== undefined) { @@ -3300,78 +3281,61 @@ function startTransition( } try { - if (enableAsyncActions) { - const returnValue = callback(); - const onStartTransitionFinish = ReactSharedInternals.S; - if (onStartTransitionFinish !== null) { - onStartTransitionFinish(currentTransition, returnValue); - } + const returnValue = callback(); + const onStartTransitionFinish = ReactSharedInternals.S; + if (onStartTransitionFinish !== null) { + onStartTransitionFinish(currentTransition, returnValue); + } - // Check if we're inside an async action scope. If so, we'll entangle - // this new action with the existing scope. - // - // If we're not already inside an async action scope, and this action is - // async, then we'll create a new async scope. - // - // In the async case, the resulting render will suspend until the async - // action scope has finished. - if ( - returnValue !== null && - typeof returnValue === 'object' && - typeof returnValue.then === 'function' - ) { - const thenable = ((returnValue: any): Thenable); - // Create a thenable that resolves to `finishedState` once the async - // action has completed. - const thenableForFinishedState = chainThenableValue( - thenable, - finishedState, - ); - dispatchSetStateInternal( - fiber, - queue, - (thenableForFinishedState: any), - requestUpdateLane(fiber), - ); - } else { - dispatchSetStateInternal( - fiber, - queue, - finishedState, - requestUpdateLane(fiber), - ); - } - } else { - // Async actions are not enabled. + // Check if we're inside an async action scope. If so, we'll entangle + // this new action with the existing scope. + // + // If we're not already inside an async action scope, and this action is + // async, then we'll create a new async scope. + // + // In the async case, the resulting render will suspend until the async + // action scope has finished. + if ( + returnValue !== null && + typeof returnValue === 'object' && + typeof returnValue.then === 'function' + ) { + const thenable = ((returnValue: any): Thenable); + // Create a thenable that resolves to `finishedState` once the async + // action has completed. + const thenableForFinishedState = chainThenableValue( + thenable, + finishedState, + ); dispatchSetStateInternal( fiber, queue, - finishedState, + (thenableForFinishedState: any), requestUpdateLane(fiber), ); - callback(); - } - } catch (error) { - if (enableAsyncActions) { - // This is a trick to get the `useTransition` hook to rethrow the error. - // When it unwraps the thenable with the `use` algorithm, the error - // will be thrown. - const rejectedThenable: RejectedThenable = { - then() {}, - status: 'rejected', - reason: error, - }; + } else { dispatchSetStateInternal( fiber, queue, - rejectedThenable, + finishedState, requestUpdateLane(fiber), ); - } else { - // The error rethrowing behavior is only enabled when the async actions - // feature is on, even for sync actions. - throw error; } + } catch (error) { + // This is a trick to get the `useTransition` hook to rethrow the error. + // When it unwraps the thenable with the `use` algorithm, the error + // will be thrown. + const rejectedThenable: RejectedThenable = { + then() {}, + status: 'rejected', + reason: error, + }; + dispatchSetStateInternal( + fiber, + queue, + rejectedThenable, + requestUpdateLane(fiber), + ); } finally { setCurrentUpdatePriority(previousPriority); @@ -3401,15 +3365,6 @@ export function startHostTransition( action: (F => mixed) | null, formData: F, ): void { - if (!enableAsyncActions) { - // Form actions are enabled, but async actions are not. Call the function, - // but don't handle any pending or error states. - if (action !== null) { - action(formData); - } - return; - } - if (formFiber.tag !== HostComponent) { throw new Error( 'Expected the form instance to be a HostComponent. This ' + @@ -3595,9 +3550,6 @@ function rerenderTransition(): [ } function useHostTransitionStatus(): TransitionStatus { - if (!enableAsyncActions) { - throw new Error('Not implemented.'); - } return readContext(HostTransitionContext); } @@ -4026,6 +3978,10 @@ export const ContextOnlyDispatcher: Dispatcher = { useTransition: throwInvalidHookError, useSyncExternalStore: throwInvalidHookError, useId: throwInvalidHookError, + useHostTransitionStatus: throwInvalidHookError, + useFormState: throwInvalidHookError, + useActionState: throwInvalidHookError, + useOptimistic: throwInvalidHookError, }; if (enableCache) { (ContextOnlyDispatcher: Dispatcher).useCacheRefresh = throwInvalidHookError; @@ -4039,15 +3995,6 @@ if (enableUseEffectEventHook) { if (enableUseResourceEffectHook) { (ContextOnlyDispatcher: Dispatcher).useResourceEffect = throwInvalidHookError; } -if (enableAsyncActions) { - (ContextOnlyDispatcher: Dispatcher).useHostTransitionStatus = - throwInvalidHookError; - (ContextOnlyDispatcher: Dispatcher).useFormState = throwInvalidHookError; - (ContextOnlyDispatcher: Dispatcher).useActionState = throwInvalidHookError; -} -if (enableAsyncActions) { - (ContextOnlyDispatcher: Dispatcher).useOptimistic = throwInvalidHookError; -} if (enableContextProfiling) { (ContextOnlyDispatcher: Dispatcher).unstable_useContextWithBailout = throwInvalidHookError; @@ -4072,6 +4019,10 @@ const HooksDispatcherOnMount: Dispatcher = { useTransition: mountTransition, useSyncExternalStore: mountSyncExternalStore, useId: mountId, + useHostTransitionStatus: useHostTransitionStatus, + useFormState: mountActionState, + useActionState: mountActionState, + useOptimistic: mountOptimistic, }; if (enableCache) { (HooksDispatcherOnMount: Dispatcher).useCacheRefresh = mountRefresh; @@ -4085,15 +4036,6 @@ if (enableUseEffectEventHook) { if (enableUseResourceEffectHook) { (HooksDispatcherOnMount: Dispatcher).useResourceEffect = mountResourceEffect; } -if (enableAsyncActions) { - (HooksDispatcherOnMount: Dispatcher).useHostTransitionStatus = - useHostTransitionStatus; - (HooksDispatcherOnMount: Dispatcher).useFormState = mountActionState; - (HooksDispatcherOnMount: Dispatcher).useActionState = mountActionState; -} -if (enableAsyncActions) { - (HooksDispatcherOnMount: Dispatcher).useOptimistic = mountOptimistic; -} if (enableContextProfiling) { (HooksDispatcherOnMount: Dispatcher).unstable_useContextWithBailout = unstable_useContextWithBailout; @@ -4118,6 +4060,10 @@ const HooksDispatcherOnUpdate: Dispatcher = { useTransition: updateTransition, useSyncExternalStore: updateSyncExternalStore, useId: updateId, + useHostTransitionStatus: useHostTransitionStatus, + useFormState: updateActionState, + useActionState: updateActionState, + useOptimistic: updateOptimistic, }; if (enableCache) { (HooksDispatcherOnUpdate: Dispatcher).useCacheRefresh = updateRefresh; @@ -4132,15 +4078,6 @@ if (enableUseResourceEffectHook) { (HooksDispatcherOnUpdate: Dispatcher).useResourceEffect = updateResourceEffect; } -if (enableAsyncActions) { - (HooksDispatcherOnUpdate: Dispatcher).useHostTransitionStatus = - useHostTransitionStatus; - (HooksDispatcherOnUpdate: Dispatcher).useFormState = updateActionState; - (HooksDispatcherOnUpdate: Dispatcher).useActionState = updateActionState; -} -if (enableAsyncActions) { - (HooksDispatcherOnUpdate: Dispatcher).useOptimistic = updateOptimistic; -} if (enableContextProfiling) { (HooksDispatcherOnUpdate: Dispatcher).unstable_useContextWithBailout = unstable_useContextWithBailout; @@ -4165,6 +4102,10 @@ const HooksDispatcherOnRerender: Dispatcher = { useTransition: rerenderTransition, useSyncExternalStore: updateSyncExternalStore, useId: updateId, + useHostTransitionStatus: useHostTransitionStatus, + useFormState: rerenderActionState, + useActionState: rerenderActionState, + useOptimistic: rerenderOptimistic, }; if (enableCache) { (HooksDispatcherOnRerender: Dispatcher).useCacheRefresh = updateRefresh; @@ -4179,15 +4120,6 @@ if (enableUseResourceEffectHook) { (HooksDispatcherOnRerender: Dispatcher).useResourceEffect = updateResourceEffect; } -if (enableAsyncActions) { - (HooksDispatcherOnRerender: Dispatcher).useHostTransitionStatus = - useHostTransitionStatus; - (HooksDispatcherOnRerender: Dispatcher).useFormState = rerenderActionState; - (HooksDispatcherOnRerender: Dispatcher).useActionState = rerenderActionState; -} -if (enableAsyncActions) { - (HooksDispatcherOnRerender: Dispatcher).useOptimistic = rerenderOptimistic; -} if (enableContextProfiling) { (HooksDispatcherOnRerender: Dispatcher).unstable_useContextWithBailout = unstable_useContextWithBailout; @@ -4347,6 +4279,34 @@ if (__DEV__) { mountHookTypesDev(); return mountId(); }, + useFormState( + action: (Awaited, P) => S, + initialState: Awaited, + permalink?: string, + ): [Awaited, (P) => void, boolean] { + currentHookNameInDev = 'useFormState'; + mountHookTypesDev(); + warnOnUseFormStateInDev(); + return mountActionState(action, initialState, permalink); + }, + useActionState( + action: (Awaited, P) => S, + initialState: Awaited, + permalink?: string, + ): [Awaited, (P) => void, boolean] { + currentHookNameInDev = 'useActionState'; + mountHookTypesDev(); + return mountActionState(action, initialState, permalink); + }, + useOptimistic( + passthrough: S, + reducer: ?(S, A) => S, + ): [S, (A) => void] { + currentHookNameInDev = 'useOptimistic'; + mountHookTypesDev(); + return mountOptimistic(passthrough, reducer); + }, + useHostTransitionStatus, }; if (enableCache) { (HooksDispatcherOnMountInDEV: Dispatcher).useCacheRefresh = @@ -4390,42 +4350,6 @@ if (__DEV__) { ); }; } - if (enableAsyncActions) { - (HooksDispatcherOnMountInDEV: Dispatcher).useHostTransitionStatus = - useHostTransitionStatus; - (HooksDispatcherOnMountInDEV: Dispatcher).useFormState = - function useFormState( - action: (Awaited, P) => S, - initialState: Awaited, - permalink?: string, - ): [Awaited, (P) => void, boolean] { - currentHookNameInDev = 'useFormState'; - mountHookTypesDev(); - warnOnUseFormStateInDev(); - return mountActionState(action, initialState, permalink); - }; - (HooksDispatcherOnMountInDEV: Dispatcher).useActionState = - function useActionState( - action: (Awaited, P) => S, - initialState: Awaited, - permalink?: string, - ): [Awaited, (P) => void, boolean] { - currentHookNameInDev = 'useActionState'; - mountHookTypesDev(); - return mountActionState(action, initialState, permalink); - }; - } - if (enableAsyncActions) { - (HooksDispatcherOnMountInDEV: Dispatcher).useOptimistic = - function useOptimistic( - passthrough: S, - reducer: ?(S, A) => S, - ): [S, (A) => void] { - currentHookNameInDev = 'useOptimistic'; - mountHookTypesDev(); - return mountOptimistic(passthrough, reducer); - }; - } if (enableContextProfiling) { (HooksDispatcherOnMountInDEV: Dispatcher).unstable_useContextWithBailout = function ( @@ -4559,6 +4483,34 @@ if (__DEV__) { updateHookTypesDev(); return mountId(); }, + useActionState( + action: (Awaited, P) => S, + initialState: Awaited, + permalink?: string, + ): [Awaited, (P) => void, boolean] { + currentHookNameInDev = 'useActionState'; + updateHookTypesDev(); + return mountActionState(action, initialState, permalink); + }, + useFormState( + action: (Awaited, P) => S, + initialState: Awaited, + permalink?: string, + ): [Awaited, (P) => void, boolean] { + currentHookNameInDev = 'useFormState'; + updateHookTypesDev(); + warnOnUseFormStateInDev(); + return mountActionState(action, initialState, permalink); + }, + useOptimistic( + passthrough: S, + reducer: ?(S, A) => S, + ): [S, (A) => void] { + currentHookNameInDev = 'useOptimistic'; + updateHookTypesDev(); + return mountOptimistic(passthrough, reducer); + }, + useHostTransitionStatus, }; if (enableCache) { (HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).useCacheRefresh = @@ -4602,42 +4554,6 @@ if (__DEV__) { ); }; } - if (enableAsyncActions) { - (HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).useHostTransitionStatus = - useHostTransitionStatus; - (HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).useFormState = - function useFormState( - action: (Awaited, P) => S, - initialState: Awaited, - permalink?: string, - ): [Awaited, (P) => void, boolean] { - currentHookNameInDev = 'useFormState'; - updateHookTypesDev(); - warnOnUseFormStateInDev(); - return mountActionState(action, initialState, permalink); - }; - (HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).useActionState = - function useActionState( - action: (Awaited, P) => S, - initialState: Awaited, - permalink?: string, - ): [Awaited, (P) => void, boolean] { - currentHookNameInDev = 'useActionState'; - updateHookTypesDev(); - return mountActionState(action, initialState, permalink); - }; - } - if (enableAsyncActions) { - (HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).useOptimistic = - function useOptimistic( - passthrough: S, - reducer: ?(S, A) => S, - ): [S, (A) => void] { - currentHookNameInDev = 'useOptimistic'; - updateHookTypesDev(); - return mountOptimistic(passthrough, reducer); - }; - } if (enableContextProfiling) { (HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).unstable_useContextWithBailout = function ( @@ -4771,6 +4687,34 @@ if (__DEV__) { updateHookTypesDev(); return updateId(); }, + useFormState( + action: (Awaited, P) => S, + initialState: Awaited, + permalink?: string, + ): [Awaited, (P) => void, boolean] { + currentHookNameInDev = 'useFormState'; + updateHookTypesDev(); + warnOnUseFormStateInDev(); + return updateActionState(action, initialState, permalink); + }, + useActionState( + action: (Awaited, P) => S, + initialState: Awaited, + permalink?: string, + ): [Awaited, (P) => void, boolean] { + currentHookNameInDev = 'useActionState'; + updateHookTypesDev(); + return updateActionState(action, initialState, permalink); + }, + useOptimistic( + passthrough: S, + reducer: ?(S, A) => S, + ): [S, (A) => void] { + currentHookNameInDev = 'useOptimistic'; + updateHookTypesDev(); + return updateOptimistic(passthrough, reducer); + }, + useHostTransitionStatus, }; if (enableCache) { (HooksDispatcherOnUpdateInDEV: Dispatcher).useCacheRefresh = @@ -4813,42 +4757,6 @@ if (__DEV__) { ); }; } - if (enableAsyncActions) { - (HooksDispatcherOnUpdateInDEV: Dispatcher).useHostTransitionStatus = - useHostTransitionStatus; - (HooksDispatcherOnUpdateInDEV: Dispatcher).useFormState = - function useFormState( - action: (Awaited, P) => S, - initialState: Awaited, - permalink?: string, - ): [Awaited, (P) => void, boolean] { - currentHookNameInDev = 'useFormState'; - updateHookTypesDev(); - warnOnUseFormStateInDev(); - return updateActionState(action, initialState, permalink); - }; - (HooksDispatcherOnUpdateInDEV: Dispatcher).useActionState = - function useActionState( - action: (Awaited, P) => S, - initialState: Awaited, - permalink?: string, - ): [Awaited, (P) => void, boolean] { - currentHookNameInDev = 'useActionState'; - updateHookTypesDev(); - return updateActionState(action, initialState, permalink); - }; - } - if (enableAsyncActions) { - (HooksDispatcherOnUpdateInDEV: Dispatcher).useOptimistic = - function useOptimistic( - passthrough: S, - reducer: ?(S, A) => S, - ): [S, (A) => void] { - currentHookNameInDev = 'useOptimistic'; - updateHookTypesDev(); - return updateOptimistic(passthrough, reducer); - }; - } if (enableContextProfiling) { (HooksDispatcherOnUpdateInDEV: Dispatcher).unstable_useContextWithBailout = function ( @@ -4982,6 +4890,34 @@ if (__DEV__) { updateHookTypesDev(); return updateId(); }, + useFormState( + action: (Awaited, P) => S, + initialState: Awaited, + permalink?: string, + ): [Awaited, (P) => void, boolean] { + currentHookNameInDev = 'useFormState'; + updateHookTypesDev(); + warnOnUseFormStateInDev(); + return rerenderActionState(action, initialState, permalink); + }, + useActionState( + action: (Awaited, P) => S, + initialState: Awaited, + permalink?: string, + ): [Awaited, (P) => void, boolean] { + currentHookNameInDev = 'useActionState'; + updateHookTypesDev(); + return rerenderActionState(action, initialState, permalink); + }, + useOptimistic( + passthrough: S, + reducer: ?(S, A) => S, + ): [S, (A) => void] { + currentHookNameInDev = 'useOptimistic'; + updateHookTypesDev(); + return rerenderOptimistic(passthrough, reducer); + }, + useHostTransitionStatus, }; if (enableCache) { (HooksDispatcherOnRerenderInDEV: Dispatcher).useCacheRefresh = @@ -5024,42 +4960,6 @@ if (__DEV__) { ); }; } - if (enableAsyncActions) { - (HooksDispatcherOnRerenderInDEV: Dispatcher).useHostTransitionStatus = - useHostTransitionStatus; - (HooksDispatcherOnRerenderInDEV: Dispatcher).useFormState = - function useFormState( - action: (Awaited, P) => S, - initialState: Awaited, - permalink?: string, - ): [Awaited, (P) => void, boolean] { - currentHookNameInDev = 'useFormState'; - updateHookTypesDev(); - warnOnUseFormStateInDev(); - return rerenderActionState(action, initialState, permalink); - }; - (HooksDispatcherOnRerenderInDEV: Dispatcher).useActionState = - function useActionState( - action: (Awaited, P) => S, - initialState: Awaited, - permalink?: string, - ): [Awaited, (P) => void, boolean] { - currentHookNameInDev = 'useActionState'; - updateHookTypesDev(); - return rerenderActionState(action, initialState, permalink); - }; - } - if (enableAsyncActions) { - (HooksDispatcherOnRerenderInDEV: Dispatcher).useOptimistic = - function useOptimistic( - passthrough: S, - reducer: ?(S, A) => S, - ): [S, (A) => void] { - currentHookNameInDev = 'useOptimistic'; - updateHookTypesDev(); - return rerenderOptimistic(passthrough, reducer); - }; - } if (enableContextProfiling) { (HooksDispatcherOnRerenderInDEV: Dispatcher).unstable_useContextWithBailout = function ( @@ -5212,6 +5112,36 @@ if (__DEV__) { mountHookTypesDev(); return mountId(); }, + useFormState( + action: (Awaited, P) => S, + initialState: Awaited, + permalink?: string, + ): [Awaited, (P) => void, boolean] { + currentHookNameInDev = 'useFormState'; + warnInvalidHookAccess(); + mountHookTypesDev(); + return mountActionState(action, initialState, permalink); + }, + useActionState( + action: (Awaited, P) => S, + initialState: Awaited, + permalink?: string, + ): [Awaited, (P) => void, boolean] { + currentHookNameInDev = 'useActionState'; + warnInvalidHookAccess(); + mountHookTypesDev(); + return mountActionState(action, initialState, permalink); + }, + useOptimistic( + passthrough: S, + reducer: ?(S, A) => S, + ): [S, (A) => void] { + currentHookNameInDev = 'useOptimistic'; + warnInvalidHookAccess(); + mountHookTypesDev(); + return mountOptimistic(passthrough, reducer); + }, + useHostTransitionStatus, }; if (enableCache) { (InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher).useCacheRefresh = @@ -5260,44 +5190,6 @@ if (__DEV__) { ); }; } - if (enableAsyncActions) { - (InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher).useHostTransitionStatus = - useHostTransitionStatus; - (InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher).useFormState = - function useFormState( - action: (Awaited, P) => S, - initialState: Awaited, - permalink?: string, - ): [Awaited, (P) => void, boolean] { - currentHookNameInDev = 'useFormState'; - warnInvalidHookAccess(); - mountHookTypesDev(); - return mountActionState(action, initialState, permalink); - }; - (InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher).useActionState = - function useActionState( - action: (Awaited, P) => S, - initialState: Awaited, - permalink?: string, - ): [Awaited, (P) => void, boolean] { - currentHookNameInDev = 'useActionState'; - warnInvalidHookAccess(); - mountHookTypesDev(); - return mountActionState(action, initialState, permalink); - }; - } - if (enableAsyncActions) { - (InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher).useOptimistic = - function useOptimistic( - passthrough: S, - reducer: ?(S, A) => S, - ): [S, (A) => void] { - currentHookNameInDev = 'useOptimistic'; - warnInvalidHookAccess(); - mountHookTypesDev(); - return mountOptimistic(passthrough, reducer); - }; - } if (enableContextProfiling) { (InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher).unstable_useContextWithBailout = function ( @@ -5451,6 +5343,36 @@ if (__DEV__) { updateHookTypesDev(); return updateId(); }, + useFormState( + action: (Awaited, P) => S, + initialState: Awaited, + permalink?: string, + ): [Awaited, (P) => void, boolean] { + currentHookNameInDev = 'useFormState'; + warnInvalidHookAccess(); + updateHookTypesDev(); + return updateActionState(action, initialState, permalink); + }, + useActionState( + action: (Awaited, P) => S, + initialState: Awaited, + permalink?: string, + ): [Awaited, (P) => void, boolean] { + currentHookNameInDev = 'useActionState'; + warnInvalidHookAccess(); + updateHookTypesDev(); + return updateActionState(action, initialState, permalink); + }, + useOptimistic( + passthrough: S, + reducer: ?(S, A) => S, + ): [S, (A) => void] { + currentHookNameInDev = 'useOptimistic'; + warnInvalidHookAccess(); + updateHookTypesDev(); + return updateOptimistic(passthrough, reducer); + }, + useHostTransitionStatus, }; if (enableCache) { (InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).useCacheRefresh = @@ -5499,44 +5421,6 @@ if (__DEV__) { ); }; } - if (enableAsyncActions) { - (InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).useHostTransitionStatus = - useHostTransitionStatus; - (InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).useFormState = - function useFormState( - action: (Awaited, P) => S, - initialState: Awaited, - permalink?: string, - ): [Awaited, (P) => void, boolean] { - currentHookNameInDev = 'useFormState'; - warnInvalidHookAccess(); - updateHookTypesDev(); - return updateActionState(action, initialState, permalink); - }; - (InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).useActionState = - function useActionState( - action: (Awaited, P) => S, - initialState: Awaited, - permalink?: string, - ): [Awaited, (P) => void, boolean] { - currentHookNameInDev = 'useActionState'; - warnInvalidHookAccess(); - updateHookTypesDev(); - return updateActionState(action, initialState, permalink); - }; - } - if (enableAsyncActions) { - (InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).useOptimistic = - function useOptimistic( - passthrough: S, - reducer: ?(S, A) => S, - ): [S, (A) => void] { - currentHookNameInDev = 'useOptimistic'; - warnInvalidHookAccess(); - updateHookTypesDev(); - return updateOptimistic(passthrough, reducer); - }; - } if (enableContextProfiling) { (InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).unstable_useContextWithBailout = function ( @@ -5690,6 +5574,36 @@ if (__DEV__) { updateHookTypesDev(); return updateId(); }, + useFormState( + action: (Awaited, P) => S, + initialState: Awaited, + permalink?: string, + ): [Awaited, (P) => void, boolean] { + currentHookNameInDev = 'useFormState'; + warnInvalidHookAccess(); + updateHookTypesDev(); + return rerenderActionState(action, initialState, permalink); + }, + useActionState( + action: (Awaited, P) => S, + initialState: Awaited, + permalink?: string, + ): [Awaited, (P) => void, boolean] { + currentHookNameInDev = 'useActionState'; + warnInvalidHookAccess(); + updateHookTypesDev(); + return rerenderActionState(action, initialState, permalink); + }, + useOptimistic( + passthrough: S, + reducer: ?(S, A) => S, + ): [S, (A) => void] { + currentHookNameInDev = 'useOptimistic'; + warnInvalidHookAccess(); + updateHookTypesDev(); + return rerenderOptimistic(passthrough, reducer); + }, + useHostTransitionStatus, }; if (enableCache) { (InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).useCacheRefresh = @@ -5738,44 +5652,6 @@ if (__DEV__) { ); }; } - if (enableAsyncActions) { - (InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).useHostTransitionStatus = - useHostTransitionStatus; - (InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).useFormState = - function useFormState( - action: (Awaited, P) => S, - initialState: Awaited, - permalink?: string, - ): [Awaited, (P) => void, boolean] { - currentHookNameInDev = 'useFormState'; - warnInvalidHookAccess(); - updateHookTypesDev(); - return rerenderActionState(action, initialState, permalink); - }; - (InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).useActionState = - function useActionState( - action: (Awaited, P) => S, - initialState: Awaited, - permalink?: string, - ): [Awaited, (P) => void, boolean] { - currentHookNameInDev = 'useActionState'; - warnInvalidHookAccess(); - updateHookTypesDev(); - return rerenderActionState(action, initialState, permalink); - }; - } - if (enableAsyncActions) { - (InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).useOptimistic = - function useOptimistic( - passthrough: S, - reducer: ?(S, A) => S, - ): [S, (A) => void] { - currentHookNameInDev = 'useOptimistic'; - warnInvalidHookAccess(); - updateHookTypesDev(); - return rerenderOptimistic(passthrough, reducer); - }; - } if (enableContextProfiling) { (InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).unstable_useContextWithBailout = function ( diff --git a/packages/react-reconciler/src/ReactFiberHostContext.js b/packages/react-reconciler/src/ReactFiberHostContext.js index a3b525b1a0bf0..10ea377fc2b39 100644 --- a/packages/react-reconciler/src/ReactFiberHostContext.js +++ b/packages/react-reconciler/src/ReactFiberHostContext.js @@ -20,7 +20,6 @@ import { isPrimaryRenderer, } from './ReactFiberConfig'; import {createCursor, push, pop} from './ReactFiberStack'; -import {enableAsyncActions} from 'shared/ReactFeatureFlags'; const contextStackCursor: StackCursor = createCursor(null); const contextFiberStackCursor: StackCursor = createCursor(null); @@ -91,13 +90,11 @@ function getHostContext(): HostContext { } function pushHostContext(fiber: Fiber): void { - if (enableAsyncActions) { - const stateHook: Hook | null = fiber.memoizedState; - if (stateHook !== null) { - // Only provide context if this fiber has been upgraded by a host - // transition. We use the same optimization for regular host context below. - push(hostTransitionProviderCursor, fiber, fiber); - } + const stateHook: Hook | null = fiber.memoizedState; + if (stateHook !== null) { + // Only provide context if this fiber has been upgraded by a host + // transition. We use the same optimization for regular host context below. + push(hostTransitionProviderCursor, fiber, fiber); } const context: HostContext = requiredContext(contextStackCursor.current); @@ -120,25 +117,23 @@ function popHostContext(fiber: Fiber): void { pop(contextFiberStackCursor, fiber); } - if (enableAsyncActions) { - if (hostTransitionProviderCursor.current === fiber) { - // Do not pop unless this Fiber provided the current context. This is mostly - // a performance optimization, but conveniently it also prevents a potential - // data race where a host provider is upgraded (i.e. memoizedState becomes - // non-null) during a concurrent event. This is a bit of a flaw in the way - // we upgrade host components, but because we're accounting for it here, it - // should be fine. - pop(hostTransitionProviderCursor, fiber); - - // When popping the transition provider, we reset the context value back - // to `NotPendingTransition`. We can do this because you're not allowed to nest forms. If - // we allowed for multiple nested host transition providers, then we'd - // need to reset this to the parent provider's status. - if (isPrimaryRenderer) { - HostTransitionContext._currentValue = NotPendingTransition; - } else { - HostTransitionContext._currentValue2 = NotPendingTransition; - } + if (hostTransitionProviderCursor.current === fiber) { + // Do not pop unless this Fiber provided the current context. This is mostly + // a performance optimization, but conveniently it also prevents a potential + // data race where a host provider is upgraded (i.e. memoizedState becomes + // non-null) during a concurrent event. This is a bit of a flaw in the way + // we upgrade host components, but because we're accounting for it here, it + // should be fine. + pop(hostTransitionProviderCursor, fiber); + + // When popping the transition provider, we reset the context value back + // to `NotPendingTransition`. We can do this because you're not allowed to nest forms. If + // we allowed for multiple nested host transition providers, then we'd + // need to reset this to the parent provider's status. + if (isPrimaryRenderer) { + HostTransitionContext._currentValue = NotPendingTransition; + } else { + HostTransitionContext._currentValue2 = NotPendingTransition; } } } diff --git a/packages/react-reconciler/src/ReactFiberNewContext.js b/packages/react-reconciler/src/ReactFiberNewContext.js index 2e8d01e99e89c..190475519e1ca 100644 --- a/packages/react-reconciler/src/ReactFiberNewContext.js +++ b/packages/react-reconciler/src/ReactFiberNewContext.js @@ -45,7 +45,6 @@ import {createUpdate, ForceUpdate} from './ReactFiberClassUpdateQueue'; import {markWorkInProgressReceivedUpdate} from './ReactFiberBeginWork'; import { enableLazyContextPropagation, - enableAsyncActions, enableRenderableContext, } from 'shared/ReactFeatureFlags'; import {getHostTransitionProvider} from './ReactFiberHostContext'; @@ -598,7 +597,7 @@ function propagateParentContextChanges( } } } - } else if (enableAsyncActions && parent === getHostTransitionProvider()) { + } else if (parent === getHostTransitionProvider()) { // During a host transition, a host component can act like a context // provider. E.g. in React DOM, this would be a
. const currentParent = parent.alternate; diff --git a/packages/react-reconciler/src/ReactFiberTransition.js b/packages/react-reconciler/src/ReactFiberTransition.js index 8ddd7083363a8..8953ca1457271 100644 --- a/packages/react-reconciler/src/ReactFiberTransition.js +++ b/packages/react-reconciler/src/ReactFiberTransition.js @@ -16,11 +16,7 @@ import type { Transition, } from './ReactFiberTracingMarkerComponent'; -import { - enableCache, - enableTransitionTracing, - enableAsyncActions, -} from 'shared/ReactFeatureFlags'; +import {enableCache, enableTransitionTracing} from 'shared/ReactFeatureFlags'; import {isPrimaryRenderer} from './ReactFiberConfig'; import {createCursor, push, pop} from './ReactFiberStack'; import { @@ -65,7 +61,6 @@ ReactSharedInternals.S = function onStartTransitionFinishForReconciler( returnValue: mixed, ) { if ( - enableAsyncActions && typeof returnValue === 'object' && returnValue !== null && typeof returnValue.then === 'function' diff --git a/packages/react-reconciler/src/ReactInternalTypes.js b/packages/react-reconciler/src/ReactInternalTypes.js index dc6ab646b5445..2ef3a2da4f535 100644 --- a/packages/react-reconciler/src/ReactInternalTypes.js +++ b/packages/react-reconciler/src/ReactInternalTypes.js @@ -448,17 +448,17 @@ export type Dispatcher = { useId(): string, useCacheRefresh?: () => (?() => T, ?T) => void, useMemoCache?: (size: number) => Array, - useHostTransitionStatus?: () => TransitionStatus, - useOptimistic?: ( + useHostTransitionStatus: () => TransitionStatus, + useOptimistic: ( passthrough: S, reducer: ?(S, A) => S, ) => [S, (A) => void], - useFormState?: ( + useFormState: ( action: (Awaited, P) => S, initialState: Awaited, permalink?: string, ) => [Awaited, (P) => void, boolean], - useActionState?: ( + useActionState: ( action: (Awaited, P) => S, initialState: Awaited, permalink?: string, diff --git a/packages/react-reconciler/src/__tests__/ReactAsyncActions-test.js b/packages/react-reconciler/src/__tests__/ReactAsyncActions-test.js index 2a65660b20f4b..843d5b2dd3fe6 100644 --- a/packages/react-reconciler/src/__tests__/ReactAsyncActions-test.js +++ b/packages/react-reconciler/src/__tests__/ReactAsyncActions-test.js @@ -121,7 +121,6 @@ describe('ReactAsyncActions', () => { return text; } - // @gate enableAsyncActions it('isPending remains true until async action finishes', async () => { let startTransition; function App() { @@ -154,7 +153,6 @@ describe('ReactAsyncActions', () => { expect(root).toMatchRenderedOutput('Pending: false'); }); - // @gate enableAsyncActions it('multiple updates in an async action scope are entangled together', async () => { let startTransition; function App({text}) { @@ -210,7 +208,6 @@ describe('ReactAsyncActions', () => { ); }); - // @gate enableAsyncActions it('multiple async action updates in the same scope are entangled together', async () => { let setStepA; function A() { @@ -350,7 +347,6 @@ describe('ReactAsyncActions', () => { ); }); - // @gate enableAsyncActions it('urgent updates are not blocked during an async action', async () => { let setStepA; function A() { @@ -431,7 +427,6 @@ describe('ReactAsyncActions', () => { ); }); - // @gate enableAsyncActions it("if a sync action throws, it's rethrown from the `useTransition`", async () => { class ErrorBoundary extends React.Component { state = {error: null}; @@ -473,7 +468,6 @@ describe('ReactAsyncActions', () => { expect(root).toMatchRenderedOutput('Oops!'); }); - // @gate enableAsyncActions it("if an async action throws, it's rethrown from the `useTransition`", async () => { class ErrorBoundary extends React.Component { state = {error: null}; @@ -521,34 +515,6 @@ describe('ReactAsyncActions', () => { expect(root).toMatchRenderedOutput('Oops!'); }); - // @gate !enableAsyncActions - it('when enableAsyncActions is disabled, and a sync action throws, `isPending` is turned off', async () => { - let startTransition; - function App() { - const [isPending, _start] = useTransition(); - startTransition = _start; - return ; - } - - const root = ReactNoop.createRoot(); - await act(() => { - root.render(); - }); - assertLog(['Pending: false']); - expect(root).toMatchRenderedOutput('Pending: false'); - - await act(() => { - expect(() => { - startTransition(() => { - throw new Error('Oops!'); - }); - }).toThrow('Oops!'); - }); - assertLog(['Pending: true', 'Pending: false']); - expect(root).toMatchRenderedOutput('Pending: false'); - }); - - // @gate enableAsyncActions it('if there are multiple entangled actions, and one of them errors, it only affects that action', async () => { class ErrorBoundary extends React.Component { state = {error: null}; @@ -665,7 +631,6 @@ describe('ReactAsyncActions', () => { ); }); - // @gate enableAsyncActions it('useOptimistic can be used to implement a pending state', async () => { const startTransition = React.startTransition; @@ -715,7 +680,6 @@ describe('ReactAsyncActions', () => { ]); }); - // @gate enableAsyncActions it('useOptimistic rebases pending updates on top of passthrough value', async () => { let serverCart = ['A']; @@ -836,7 +800,6 @@ describe('ReactAsyncActions', () => { ); }); - // @gate enableAsyncActions it( 'regression: when there are no pending transitions, useOptimistic should ' + 'always return the passthrough value', @@ -882,7 +845,6 @@ describe('ReactAsyncActions', () => { }, ); - // @gate enableAsyncActions it('regression: useOptimistic during setState-in-render', async () => { // This is a regression test for a very specific case where useOptimistic is // the first hook in the component, it has a pending update, and a later @@ -920,7 +882,6 @@ describe('ReactAsyncActions', () => { expect(root).toMatchRenderedOutput('1'); }); - // @gate enableAsyncActions it('useOptimistic accepts a custom reducer', async () => { let serverCart = ['A']; @@ -1052,7 +1013,6 @@ describe('ReactAsyncActions', () => { ); }); - // @gate enableAsyncActions it('useOptimistic rebases if the passthrough is updated during a render phase update', async () => { // This is kind of an esoteric case where it's hard to come up with a // realistic real-world scenario but it should still work. @@ -1137,7 +1097,6 @@ describe('ReactAsyncActions', () => { expect(root).toMatchRenderedOutput(
Count: 3
); }); - // @gate enableAsyncActions it('useOptimistic rebases if the passthrough is updated during a render phase update (initial mount)', async () => { // This is kind of an esoteric case where it's hard to come up with a // realistic real-world scenario but it should still work. @@ -1177,7 +1136,6 @@ describe('ReactAsyncActions', () => { ); }); - // @gate enableAsyncActions it('useOptimistic can update repeatedly in the same async action', async () => { let startTransition; let setLoadingProgress; @@ -1241,7 +1199,6 @@ describe('ReactAsyncActions', () => { expect(root).toMatchRenderedOutput(
B
); }); - // @gate enableAsyncActions it('useOptimistic warns if outside of a transition', async () => { let startTransition; let setLoadingProgress; @@ -1289,7 +1246,6 @@ describe('ReactAsyncActions', () => { expect(root).toMatchRenderedOutput(
B
); }); - // @gate enableAsyncActions it( 'optimistic state is not reverted until async action finishes, even if ' + 'useTransition hook is unmounted', @@ -1392,7 +1348,6 @@ describe('ReactAsyncActions', () => { }, ); - // @gate enableAsyncActions it( 'updates in an async action are entangled even if useTransition hook ' + 'is unmounted before it finishes', @@ -1480,7 +1435,6 @@ describe('ReactAsyncActions', () => { }, ); - // @gate enableAsyncActions it( 'updates in an async action are entangled even if useTransition hook ' + 'is unmounted before it finishes (class component)', @@ -1575,7 +1529,6 @@ describe('ReactAsyncActions', () => { }, ); - // @gate enableAsyncActions it( 'updates in an async action are entangled even if useTransition hook ' + 'is unmounted before it finishes (root update)', @@ -1660,7 +1613,6 @@ describe('ReactAsyncActions', () => { }, ); - // @gate enableAsyncActions it('React.startTransition supports async actions', async () => { const startTransition = React.startTransition; @@ -1698,7 +1650,6 @@ describe('ReactAsyncActions', () => { expect(root).toMatchRenderedOutput('C'); }); - // @gate enableAsyncActions it('useOptimistic works with async actions passed to React.startTransition', async () => { const startTransition = React.startTransition; @@ -1745,7 +1696,6 @@ describe('ReactAsyncActions', () => { expect(root).toMatchRenderedOutput(Updated); }); - // @gate enableAsyncActions it( 'regression: updates in an action passed to React.startTransition are batched ' + 'even if there were no updates before the first await', @@ -1816,19 +1766,14 @@ describe('ReactAsyncActions', () => { ); it('React.startTransition captures async errors and passes them to reportError', async () => { - // NOTE: This is gated here instead of using the pragma because the failure - // happens asynchronously and the `gate` runtime doesn't capture it. - if (gate(flags => flags.enableAsyncActions)) { - await act(() => { - React.startTransition(async () => { - throw new Error('Oops'); - }); + await act(() => { + React.startTransition(async () => { + throw new Error('Oops'); }); - assertLog(['reportError: Oops']); - } + }); + assertLog(['reportError: Oops']); }); - // @gate enableAsyncActions it('React.startTransition captures sync errors and passes them to reportError', async () => { await act(() => { try { diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMForm-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMForm-test.js index b33e2a38ed305..f98b79dd8a890 100644 --- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMForm-test.js +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMForm-test.js @@ -361,7 +361,6 @@ describe('ReactFlightDOMForm', () => { expect(foo).toBe('barobject'); }); - // @gate enableAsyncActions it("useActionState's dispatch binds the initial state to the provided action", async () => { const serverAction = serverExports( async function action(prevState, formData) { @@ -409,7 +408,6 @@ describe('ReactFlightDOMForm', () => { expect(await returnValue).toEqual({count: 6}); }); - // @gate enableAsyncActions it('useActionState can reuse state during MPA form submission', async () => { const serverAction = serverExports( async function action(prevState, formData) { @@ -498,7 +496,6 @@ describe('ReactFlightDOMForm', () => { } }); - // @gate enableAsyncActions it( 'useActionState preserves state if arity is the same, but different ' + 'arguments are bound (i.e. inline closure)', @@ -617,7 +614,6 @@ describe('ReactFlightDOMForm', () => { }, ); - // @gate enableAsyncActions it('useActionState does not reuse state if action signatures are different', async () => { // This is the same as the previous test, except instead of using bind to // configure the server action (i.e. a closure), it swaps the action. @@ -704,7 +700,6 @@ describe('ReactFlightDOMForm', () => { expect(container.textContent).toBe('111'); }); - // @gate enableAsyncActions it('when permalink is provided, useActionState compares that instead of the keypath', async () => { const serverAction = serverExports( async function action(prevState, formData) { @@ -810,7 +805,6 @@ describe('ReactFlightDOMForm', () => { expect(container.textContent).toBe('1'); }); - // @gate enableAsyncActions it('useActionState can change the action URL with the `permalink` argument', async () => { const serverAction = serverExports(function action(prevState) { return {state: prevState.count + 1}; @@ -855,7 +849,6 @@ describe('ReactFlightDOMForm', () => { expect(form.action).toBe('http://localhost/permalink'); }); - // @gate enableAsyncActions it('useActionState `permalink` is coerced to string', async () => { const serverAction = serverExports(function action(prevState) { return {state: prevState.count + 1}; @@ -908,7 +901,6 @@ describe('ReactFlightDOMForm', () => { expect(form.action).toBe('http://localhost/permalink'); }); - // @gate enableAsyncActions it('useActionState can return JSX state during MPA form submission', async () => { const serverAction = serverExports( async function action(prevState, formData) { @@ -980,7 +972,7 @@ describe('ReactFlightDOMForm', () => { expect(form2.firstChild.tagName).toBe('DIV'); }); - // @gate enableAsyncActions && enableBinaryFlight + // @gate enableBinaryFlight it('useActionState can return binary state during MPA form submission', async () => { const serverAction = serverExports( async function action(prevState, formData) { diff --git a/packages/react-server/src/ReactFizzHooks.js b/packages/react-server/src/ReactFizzHooks.js index 84b9448ca32f6..e4e81af8fabb8 100644 --- a/packages/react-server/src/ReactFizzHooks.js +++ b/packages/react-server/src/ReactFizzHooks.js @@ -42,7 +42,6 @@ import { enableCache, enableUseEffectEventHook, enableUseMemoCacheHook, - enableAsyncActions, enableUseResourceEffectHook, } from 'shared/ReactFeatureFlags'; import is from 'shared/objectIs'; @@ -833,6 +832,10 @@ export const HooksDispatcher: Dispatcher = supportsClientAPIs useId, // Subscriptions are not setup in a server environment. useSyncExternalStore, + useOptimistic, + useActionState, + useFormState: useActionState, + useHostTransitionStatus, } : { readContext, @@ -852,6 +855,10 @@ export const HooksDispatcher: Dispatcher = supportsClientAPIs useTransition: clientHookNotSupported, useId, useSyncExternalStore: clientHookNotSupported, + useOptimistic, + useActionState, + useFormState: useActionState, + useHostTransitionStatus, }; if (enableCache) { @@ -863,14 +870,6 @@ if (enableUseEffectEventHook) { if (enableUseMemoCacheHook) { HooksDispatcher.useMemoCache = useMemoCache; } -if (enableAsyncActions) { - HooksDispatcher.useHostTransitionStatus = useHostTransitionStatus; -} -if (enableAsyncActions) { - HooksDispatcher.useOptimistic = useOptimistic; - HooksDispatcher.useFormState = useActionState; - HooksDispatcher.useActionState = useActionState; -} if (enableUseResourceEffectHook) { HooksDispatcher.useResourceEffect = supportsClientAPIs ? noop diff --git a/packages/react-server/src/ReactFlightHooks.js b/packages/react-server/src/ReactFlightHooks.js index e85e0e9ce6a60..d0351e38c86aa 100644 --- a/packages/react-server/src/ReactFlightHooks.js +++ b/packages/react-server/src/ReactFlightHooks.js @@ -77,6 +77,10 @@ export const HooksDispatcher: Dispatcher = { useImperativeHandle: (unsupportedHook: any), useEffect: (unsupportedHook: any), useId, + useHostTransitionStatus: (unsupportedHook: any), + useOptimistic: (unsupportedHook: any), + useFormState: (unsupportedHook: any), + useActionState: (unsupportedHook: any), useSyncExternalStore: (unsupportedHook: any), useCacheRefresh(): (?() => T, ?T) => void { return unsupportedRefresh; diff --git a/packages/react/src/ReactHooks.js b/packages/react/src/ReactHooks.js index 90b904bb42c75..40c8a23d3798b 100644 --- a/packages/react/src/ReactHooks.js +++ b/packages/react/src/ReactHooks.js @@ -18,10 +18,7 @@ import {REACT_CONSUMER_TYPE} from 'shared/ReactSymbols'; import ReactSharedInternals from 'shared/ReactSharedInternals'; -import { - enableAsyncActions, - enableUseResourceEffectHook, -} from 'shared/ReactFeatureFlags'; +import {enableUseResourceEffectHook} from 'shared/ReactFeatureFlags'; import { enableContextProfiling, enableLazyContextPropagation, @@ -255,7 +252,6 @@ export function useOptimistic( reducer: ?(S, A) => S, ): [S, (A) => void] { const dispatcher = resolveDispatcher(); - // $FlowFixMe[not-a-function] This is unstable, thus optional return dispatcher.useOptimistic(passthrough, reducer); } @@ -264,11 +260,6 @@ export function useActionState( initialState: Awaited, permalink?: string, ): [Awaited, (P) => void, boolean] { - if (!enableAsyncActions) { - throw new Error('Not implemented.'); - } else { - const dispatcher = resolveDispatcher(); - // $FlowFixMe[not-a-function] This is unstable, thus optional - return dispatcher.useActionState(action, initialState, permalink); - } + const dispatcher = resolveDispatcher(); + return dispatcher.useActionState(action, initialState, permalink); } diff --git a/packages/react/src/ReactStartTransition.js b/packages/react/src/ReactStartTransition.js index a9b30c424a07f..99ccbb959b015 100644 --- a/packages/react/src/ReactStartTransition.js +++ b/packages/react/src/ReactStartTransition.js @@ -11,10 +11,7 @@ import type {StartTransitionOptions} from 'shared/ReactTypes'; import ReactSharedInternals from 'shared/ReactSharedInternals'; -import { - enableAsyncActions, - enableTransitionTracing, -} from 'shared/ReactFeatureFlags'; +import {enableTransitionTracing} from 'shared/ReactFeatureFlags'; import reportGlobalError from 'shared/reportGlobalError'; @@ -37,35 +34,24 @@ export function startTransition( } } - if (enableAsyncActions) { - try { - const returnValue = scope(); - const onStartTransitionFinish = ReactSharedInternals.S; - if (onStartTransitionFinish !== null) { - onStartTransitionFinish(currentTransition, returnValue); - } - if ( - typeof returnValue === 'object' && - returnValue !== null && - typeof returnValue.then === 'function' - ) { - returnValue.then(noop, reportGlobalError); - } - } catch (error) { - reportGlobalError(error); - } finally { - warnAboutTransitionSubscriptions(prevTransition, currentTransition); - ReactSharedInternals.T = prevTransition; + try { + const returnValue = scope(); + const onStartTransitionFinish = ReactSharedInternals.S; + if (onStartTransitionFinish !== null) { + onStartTransitionFinish(currentTransition, returnValue); } - } else { - // When async actions are not enabled, startTransition does not - // capture errors. - try { - scope(); - } finally { - warnAboutTransitionSubscriptions(prevTransition, currentTransition); - ReactSharedInternals.T = prevTransition; + if ( + typeof returnValue === 'object' && + returnValue !== null && + typeof returnValue.then === 'function' + ) { + returnValue.then(noop, reportGlobalError); } + } catch (error) { + reportGlobalError(error); + } finally { + warnAboutTransitionSubscriptions(prevTransition, currentTransition); + ReactSharedInternals.T = prevTransition; } } diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index edaf4b2419136..a96d0e1917434 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -31,7 +31,6 @@ export const enableComponentStackLocations = true; // TODO: Finish rolling out in www export const favorSafetyOverHydrationPerf = true; -export const enableAsyncActions = true; // Need to remove didTimeout argument from Scheduler before landing export const disableSchedulerTimeoutInWorkLoop = false; diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index 1ecbcfe28f5ce..eb3f5f3cd5553 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -42,7 +42,6 @@ export const disableLegacyContextForFunctionComponents = false; export const disableLegacyMode = false; export const disableSchedulerTimeoutInWorkLoop = false; export const disableTextareaChildren = false; -export const enableAsyncActions = true; export const enableAsyncDebugInfo = false; export const enableAsyncIterableChildren = false; export const enableBinaryFlight = true; diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index 0d97b0b68d263..d725ecba679ce 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -29,7 +29,6 @@ export const disableLegacyContextForFunctionComponents = true; export const disableLegacyMode = false; export const disableSchedulerTimeoutInWorkLoop = false; export const disableTextareaChildren = false; -export const enableAsyncActions = true; export const enableAsyncDebugInfo = false; export const enableAsyncIterableChildren = false; export const enableBinaryFlight = true; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index 28f96fb84e6bc..2c6892de26d96 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -67,8 +67,6 @@ export const enableDO_NOT_USE_disableStrictPassiveEffect = false; export const enableFizzExternalRuntime = true; export const enableDeferRootSchedulingToMicrotask = true; -export const enableAsyncActions = true; - export const alwaysThrottleRetries = true; export const passChildrenWhenCloningPersistedNodes = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js index fe3e2bd4813b8..eff87c5683fc8 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js @@ -21,7 +21,6 @@ export const disableLegacyContextForFunctionComponents = false; export const disableLegacyMode = false; export const disableSchedulerTimeoutInWorkLoop = false; export const disableTextareaChildren = false; -export const enableAsyncActions = true; export const enableAsyncDebugInfo = false; export const enableAsyncIterableChildren = false; export const enableBinaryFlight = true; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js index 1badea3abf309..00a0463353dda 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js @@ -69,8 +69,6 @@ export const enableDO_NOT_USE_disableStrictPassiveEffect = false; export const enableFizzExternalRuntime = false; export const enableDeferRootSchedulingToMicrotask = true; -export const enableAsyncActions = true; - export const alwaysThrottleRetries = true; export const passChildrenWhenCloningPersistedNodes = false; diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js index 2a432305635d3..65edbea8e153a 100644 --- a/packages/shared/forks/ReactFeatureFlags.www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -58,7 +58,6 @@ export const enableUseMemoCacheHook = true; export const enableUseEffectEventHook = true; export const enableFilterEmptyStringAttributesDOM = true; export const enableMoveBefore = false; -export const enableAsyncActions = true; export const disableInputAttributeSyncing = false; export const enableLegacyFBSupport = true; export const enableLazyContextPropagation = true;