From 09174670831e9c6db3eb6110f59e7ab453a62202 Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Tue, 10 Oct 2023 13:48:13 -0400 Subject: [PATCH 1/2] Add initialValue option to useDeferredValue Adds a new argument to useDeferredValue, `initialValue`, that is used during initial mount, but does not implement it yet. This is an experimental feature and is disabled in the canary build. --- .../react-debug-tools/src/ReactDebugHooks.js | 2 +- .../react-reconciler/src/ReactFiberHooks.js | 45 ++++++++++--------- .../src/ReactInternalTypes.js | 2 +- packages/react-server/src/ReactFizzHooks.js | 2 +- packages/react/src/ReactHooks.js | 4 +- packages/shared/ReactFeatureFlags.js | 2 + .../forks/ReactFeatureFlags.native-fb.js | 1 + .../forks/ReactFeatureFlags.native-oss.js | 1 + .../forks/ReactFeatureFlags.test-renderer.js | 1 + .../ReactFeatureFlags.test-renderer.native.js | 1 + .../ReactFeatureFlags.test-renderer.www.js | 1 + .../forks/ReactFeatureFlags.www-dynamic.js | 1 + .../shared/forks/ReactFeatureFlags.www.js | 1 + 13 files changed, 39 insertions(+), 25 deletions(-) diff --git a/packages/react-debug-tools/src/ReactDebugHooks.js b/packages/react-debug-tools/src/ReactDebugHooks.js index 6c8acd1e2f92..2d75ff127e7b 100644 --- a/packages/react-debug-tools/src/ReactDebugHooks.js +++ b/packages/react-debug-tools/src/ReactDebugHooks.js @@ -306,7 +306,7 @@ function useTransition(): [ return [false, callback => {}]; } -function useDeferredValue(value: T): T { +function useDeferredValue(value: T, initialValue?: T): T { const hook = nextHook(); hookLog.push({ primitive: 'DeferredValue', diff --git a/packages/react-reconciler/src/ReactFiberHooks.js b/packages/react-reconciler/src/ReactFiberHooks.js index ae4b68b592d5..8f4f2492855c 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.js +++ b/packages/react-reconciler/src/ReactFiberHooks.js @@ -2638,20 +2638,20 @@ function updateMemo( return nextValue; } -function mountDeferredValue(value: T): T { +function mountDeferredValue(value: T, initialValue?: T): T { const hook = mountWorkInProgressHook(); hook.memoizedState = value; return value; } -function updateDeferredValue(value: T): T { +function updateDeferredValue(value: T, initialValue?: T): T { const hook = updateWorkInProgressHook(); const resolvedCurrentHook: Hook = (currentHook: any); const prevValue: T = resolvedCurrentHook.memoizedState; - return updateDeferredValueImpl(hook, prevValue, value); + return updateDeferredValueImpl(hook, prevValue, value, initialValue); } -function rerenderDeferredValue(value: T): T { +function rerenderDeferredValue(value: T, initialValue?: T): T { const hook = updateWorkInProgressHook(); if (currentHook === null) { // This is a rerender during a mount. @@ -2660,11 +2660,16 @@ function rerenderDeferredValue(value: T): T { } else { // This is a rerender during an update. const prevValue: T = currentHook.memoizedState; - return updateDeferredValueImpl(hook, prevValue, value); + return updateDeferredValueImpl(hook, prevValue, value, initialValue); } } -function updateDeferredValueImpl(hook: Hook, prevValue: T, value: T): T { +function updateDeferredValueImpl( + hook: Hook, + prevValue: T, + value: T, + initialValue: ?T, +): T { const shouldDeferValue = !includesOnlyNonUrgentLanes(renderLanes); if (shouldDeferValue) { // This is an urgent update. If the value has changed, keep using the @@ -3633,10 +3638,10 @@ if (__DEV__) { mountHookTypesDev(); return mountDebugValue(value, formatterFn); }, - useDeferredValue(value: T): T { + useDeferredValue(value: T, initialValue?: T): T { currentHookNameInDev = 'useDeferredValue'; mountHookTypesDev(); - return mountDeferredValue(value); + return mountDeferredValue(value, initialValue); }, useTransition(): [boolean, (() => void) => void] { currentHookNameInDev = 'useTransition'; @@ -3802,10 +3807,10 @@ if (__DEV__) { updateHookTypesDev(); return mountDebugValue(value, formatterFn); }, - useDeferredValue(value: T): T { + useDeferredValue(value: T, initialValue?: T): T { currentHookNameInDev = 'useDeferredValue'; updateHookTypesDev(); - return mountDeferredValue(value); + return mountDeferredValue(value, initialValue); }, useTransition(): [boolean, (() => void) => void] { currentHookNameInDev = 'useTransition'; @@ -3975,10 +3980,10 @@ if (__DEV__) { updateHookTypesDev(); return updateDebugValue(value, formatterFn); }, - useDeferredValue(value: T): T { + useDeferredValue(value: T, initialValue?: T): T { currentHookNameInDev = 'useDeferredValue'; updateHookTypesDev(); - return updateDeferredValue(value); + return updateDeferredValue(value, initialValue); }, useTransition(): [boolean, (() => void) => void] { currentHookNameInDev = 'useTransition'; @@ -4147,10 +4152,10 @@ if (__DEV__) { updateHookTypesDev(); return updateDebugValue(value, formatterFn); }, - useDeferredValue(value: T): T { + useDeferredValue(value: T, initialValue?: T): T { currentHookNameInDev = 'useDeferredValue'; updateHookTypesDev(); - return rerenderDeferredValue(value); + return rerenderDeferredValue(value, initialValue); }, useTransition(): [boolean, (() => void) => void] { currentHookNameInDev = 'useTransition'; @@ -4331,11 +4336,11 @@ if (__DEV__) { mountHookTypesDev(); return mountDebugValue(value, formatterFn); }, - useDeferredValue(value: T): T { + useDeferredValue(value: T, initialValue?: T): T { currentHookNameInDev = 'useDeferredValue'; warnInvalidHookAccess(); mountHookTypesDev(); - return mountDeferredValue(value); + return mountDeferredValue(value, initialValue); }, useTransition(): [boolean, (() => void) => void] { currentHookNameInDev = 'useTransition'; @@ -4529,11 +4534,11 @@ if (__DEV__) { updateHookTypesDev(); return updateDebugValue(value, formatterFn); }, - useDeferredValue(value: T): T { + useDeferredValue(value: T, initialValue?: T): T { currentHookNameInDev = 'useDeferredValue'; warnInvalidHookAccess(); updateHookTypesDev(); - return updateDeferredValue(value); + return updateDeferredValue(value, initialValue); }, useTransition(): [boolean, (() => void) => void] { currentHookNameInDev = 'useTransition'; @@ -4727,11 +4732,11 @@ if (__DEV__) { updateHookTypesDev(); return updateDebugValue(value, formatterFn); }, - useDeferredValue(value: T): T { + useDeferredValue(value: T, initialValue?: T): T { currentHookNameInDev = 'useDeferredValue'; warnInvalidHookAccess(); updateHookTypesDev(); - return rerenderDeferredValue(value); + return rerenderDeferredValue(value, initialValue); }, useTransition(): [boolean, (() => void) => void] { currentHookNameInDev = 'useTransition'; diff --git a/packages/react-reconciler/src/ReactInternalTypes.js b/packages/react-reconciler/src/ReactInternalTypes.js index b603a9fec332..9378a41477e0 100644 --- a/packages/react-reconciler/src/ReactInternalTypes.js +++ b/packages/react-reconciler/src/ReactInternalTypes.js @@ -399,7 +399,7 @@ export type Dispatcher = { deps: Array | void | null, ): void, useDebugValue(value: T, formatterFn: ?(value: T) => mixed): void, - useDeferredValue(value: T): T, + useDeferredValue(value: T, initialValue?: T): T, useTransition(): [ boolean, (callback: () => void, options?: StartTransitionOptions) => void, diff --git a/packages/react-server/src/ReactFizzHooks.js b/packages/react-server/src/ReactFizzHooks.js index c80ac1e3b621..3ecf1baa50a7 100644 --- a/packages/react-server/src/ReactFizzHooks.js +++ b/packages/react-server/src/ReactFizzHooks.js @@ -553,7 +553,7 @@ function useSyncExternalStore( return getServerSnapshot(); } -function useDeferredValue(value: T): T { +function useDeferredValue(value: T, initialValue?: T): T { resolveCurrentlyRenderingComponent(); return value; } diff --git a/packages/react/src/ReactHooks.js b/packages/react/src/ReactHooks.js index 112baab1eaa5..a2141cbc5dfb 100644 --- a/packages/react/src/ReactHooks.js +++ b/packages/react/src/ReactHooks.js @@ -181,9 +181,9 @@ export function useTransition(): [ return dispatcher.useTransition(); } -export function useDeferredValue(value: T): T { +export function useDeferredValue(value: T, initialValue?: T): T { const dispatcher = resolveDispatcher(); - return dispatcher.useDeferredValue(value); + return dispatcher.useDeferredValue(value, initialValue); } export function useId(): string { diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index 5ca0e6e291fc..751028ab2976 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -126,6 +126,8 @@ export const useMicrotasksForSchedulingInFabric = false; export const passChildrenWhenCloningPersistedNodes = false; +export const enableUseDeferredValueInitialArg = __EXPERIMENTAL__; + // ----------------------------------------------------------------------------- // Chopping Block // diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index 99d739271925..c2aa5f932333 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -89,6 +89,7 @@ export const enableDO_NOT_USE_disableStrictPassiveEffect = false; export const enableFizzExternalRuntime = false; export const enableAsyncActions = false; +export const enableUseDeferredValueInitialArg = true; // Flow magic to verify the exports of this file match the original version. ((((null: any): ExportsType): FeatureFlagsType): ExportsType); diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index ee0b2d91d387..912d1b685100 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -80,6 +80,7 @@ export const alwaysThrottleRetries = true; export const useMicrotasksForSchedulingInFabric = false; export const passChildrenWhenCloningPersistedNodes = false; +export const enableUseDeferredValueInitialArg = __EXPERIMENTAL__; // Flow magic to verify the exports of this file match the original version. ((((null: any): ExportsType): FeatureFlagsType): ExportsType); diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index 5d541fa3948e..70ea8e520466 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -80,6 +80,7 @@ export const alwaysThrottleRetries = true; export const useMicrotasksForSchedulingInFabric = false; export const passChildrenWhenCloningPersistedNodes = false; +export const enableUseDeferredValueInitialArg = __EXPERIMENTAL__; // Flow magic to verify the exports of this file match the original version. ((((null: any): ExportsType): FeatureFlagsType): ExportsType); diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js index 23a44293eeb6..6ec32f3ef534 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js @@ -77,6 +77,7 @@ export const alwaysThrottleRetries = true; export const useMicrotasksForSchedulingInFabric = false; export const passChildrenWhenCloningPersistedNodes = false; +export const enableUseDeferredValueInitialArg = __EXPERIMENTAL__; // Flow magic to verify the exports of this file match the original version. ((((null: any): ExportsType): FeatureFlagsType): ExportsType); diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js index 121442de9660..60b2373dcf49 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js @@ -80,6 +80,7 @@ export const alwaysThrottleRetries = true; export const useMicrotasksForSchedulingInFabric = false; export const passChildrenWhenCloningPersistedNodes = false; +export const enableUseDeferredValueInitialArg = true; // Flow magic to verify the exports of this file match the original version. ((((null: any): ExportsType): FeatureFlagsType): ExportsType); diff --git a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js index db3adeb56d77..1fb76bf4b31d 100644 --- a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js +++ b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js @@ -28,6 +28,7 @@ export const enableDeferRootSchedulingToMicrotask = __VARIANT__; export const enableAsyncActions = __VARIANT__; export const alwaysThrottleRetries = __VARIANT__; export const enableDO_NOT_USE_disableStrictPassiveEffect = __VARIANT__; +export const enableUseDeferredValueInitialArg = __VARIANT__; // Enable this flag to help with concurrent mode debugging. // It logs information to the console about React scheduling, rendering, and commit phases. diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js index a004364fa4ab..f3f036911d9a 100644 --- a/packages/shared/forks/ReactFeatureFlags.www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -31,6 +31,7 @@ export const { alwaysThrottleRetries, enableDO_NOT_USE_disableStrictPassiveEffect, disableSchedulerTimeoutInWorkLoop, + enableUseDeferredValueInitialArg, } = dynamicFeatureFlags; // On WWW, __EXPERIMENTAL__ is used for a new modern build. From f5c6513af561aac126a980286654929913e7c343 Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Tue, 10 Oct 2023 14:28:41 -0400 Subject: [PATCH 2/2] Implement useDeferredValue initialValue option Adds a second argument to useDeferredValue called initialValue: ```js const value = useDeferredValue(finalValue, initialValue); ``` During the initial render of a component, useDeferredValue will return initialValue. Once that render finishes, it will spawn an additional render to switch to finalValue. This same sequence should occur whenever the hook is hidden and revealed again, i.e. by a Suspense or Activity, though this part is not yet implemented. When initialValue is not provided, useDeferredValue has no effect during initial render, but during an update, it will remain on the previous value, then spawn an additional render to switch to the new value. During SSR, initialValue is always used, if provided. This feature is currently behind an experimental flag. We plan to ship it in a non-breaking release. --- .../ReactHooksInspectionIntegration-test.js | 4 +- .../ReactDOMFizzDeferredValue-test.js | 71 +++++++++++++++++++ .../react-reconciler/src/ReactFiberHooks.js | 40 +++++++++-- .../src/__tests__/ReactDeferredValue-test.js | 35 +++++++++ .../ReactHooksWithNoopRenderer-test.js | 4 +- packages/react-server/src/ReactFizzHooks.js | 7 +- 6 files changed, 150 insertions(+), 11 deletions(-) create mode 100644 packages/react-dom/src/__tests__/ReactDOMFizzDeferredValue-test.js diff --git a/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js b/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js index 7283f3af7216..3d5cd38f82eb 100644 --- a/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js +++ b/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js @@ -573,9 +573,7 @@ describe('ReactHooksInspectionIntegration', () => { it('should support useDeferredValue hook', () => { function Foo(props) { - React.useDeferredValue('abc', { - timeoutMs: 500, - }); + React.useDeferredValue('abc'); const memoizedValue = React.useMemo(() => 1, []); React.useMemo(() => 2, []); return
{memoizedValue}
; diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzDeferredValue-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzDeferredValue-test.js new file mode 100644 index 000000000000..4f8a4d98d654 --- /dev/null +++ b/packages/react-dom/src/__tests__/ReactDOMFizzDeferredValue-test.js @@ -0,0 +1,71 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core + */ + +'use strict'; + +import {insertNodesAndExecuteScripts} from '../test-utils/FizzTestUtils'; + +// Polyfills for test environment +global.ReadableStream = + require('web-streams-polyfill/ponyfill/es6').ReadableStream; +global.TextEncoder = require('util').TextEncoder; + +let act; +let container; +let React; +let ReactDOMServer; +let ReactDOMClient; +let useDeferredValue; + +describe('ReactDOMFizzForm', () => { + beforeEach(() => { + jest.resetModules(); + React = require('react'); + ReactDOMServer = require('react-dom/server.browser'); + ReactDOMClient = require('react-dom/client'); + useDeferredValue = require('react').useDeferredValue; + act = require('internal-test-utils').act; + container = document.createElement('div'); + document.body.appendChild(container); + }); + + afterEach(() => { + document.body.removeChild(container); + }); + + async function readIntoContainer(stream) { + const reader = stream.getReader(); + let result = ''; + while (true) { + const {done, value} = await reader.read(); + if (done) { + break; + } + result += Buffer.from(value).toString('utf8'); + } + const temp = document.createElement('div'); + temp.innerHTML = result; + insertNodesAndExecuteScripts(temp, container, null); + } + + // @gate enableUseDeferredValueInitialArg + it('returns initialValue argument, if provided', async () => { + function App() { + return useDeferredValue('Final', 'Initial'); + } + + const stream = await ReactDOMServer.renderToReadableStream(); + await readIntoContainer(stream); + expect(container.textContent).toEqual('Initial'); + + // After hydration, it's updated to the final value + await act(() => ReactDOMClient.hydrateRoot(container, )); + expect(container.textContent).toEqual('Final'); + }); +}); diff --git a/packages/react-reconciler/src/ReactFiberHooks.js b/packages/react-reconciler/src/ReactFiberHooks.js index 8f4f2492855c..e0f5d268bcf5 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.js +++ b/packages/react-reconciler/src/ReactFiberHooks.js @@ -41,6 +41,7 @@ import { debugRenderPhaseSideEffectsForStrictMode, enableAsyncActions, enableFormActions, + enableUseDeferredValueInitialArg, } from 'shared/ReactFeatureFlags'; import { REACT_CONTEXT_TYPE, @@ -2640,8 +2641,7 @@ function updateMemo( function mountDeferredValue(value: T, initialValue?: T): T { const hook = mountWorkInProgressHook(); - hook.memoizedState = value; - return value; + return mountDeferredValueImpl(hook, value, initialValue); } function updateDeferredValue(value: T, initialValue?: T): T { @@ -2655,8 +2655,7 @@ function rerenderDeferredValue(value: T, initialValue?: T): T { const hook = updateWorkInProgressHook(); if (currentHook === null) { // This is a rerender during a mount. - hook.memoizedState = value; - return value; + return mountDeferredValueImpl(hook, value, initialValue); } else { // This is a rerender during an update. const prevValue: T = currentHook.memoizedState; @@ -2664,12 +2663,45 @@ function rerenderDeferredValue(value: T, initialValue?: T): T { } } +function mountDeferredValueImpl(hook: Hook, value: T, initialValue?: T): T { + if (enableUseDeferredValueInitialArg && initialValue !== undefined) { + // When `initialValue` is provided, we defer the initial render even if the + // current render is not synchronous. + // TODO: However, to avoid waterfalls, we should not defer if this render + // was itself spawned by an earlier useDeferredValue. Plan is to add a + // Deferred lane to track this. + hook.memoizedState = initialValue; + + // Schedule a deferred render + const deferredLane = claimNextTransitionLane(); + currentlyRenderingFiber.lanes = mergeLanes( + currentlyRenderingFiber.lanes, + deferredLane, + ); + markSkippedUpdateLanes(deferredLane); + + // Set this to true to indicate that the rendered value is inconsistent + // from the latest value. The name "baseState" doesn't really match how we + // use it because we're reusing a state hook field instead of creating a + // new one. + hook.baseState = true; + + return initialValue; + } else { + hook.memoizedState = value; + return value; + } +} + function updateDeferredValueImpl( hook: Hook, prevValue: T, value: T, initialValue: ?T, ): T { + // TODO: We should also check if this component is going from + // hidden -> visible. If so, it should use the initialValue arg. + const shouldDeferValue = !includesOnlyNonUrgentLanes(renderLanes); if (shouldDeferValue) { // This is an urgent update. If the value has changed, keep using the diff --git a/packages/react-reconciler/src/__tests__/ReactDeferredValue-test.js b/packages/react-reconciler/src/__tests__/ReactDeferredValue-test.js index 54f4ad42f44b..c29a9c428727 100644 --- a/packages/react-reconciler/src/__tests__/ReactDeferredValue-test.js +++ b/packages/react-reconciler/src/__tests__/ReactDeferredValue-test.js @@ -306,4 +306,39 @@ describe('ReactDeferredValue', () => { ); }); }); + + // @gate enableUseDeferredValueInitialArg + it('supports initialValue argument', async () => { + function App() { + const value = useDeferredValue('Final', 'Initial'); + return ; + } + + const root = ReactNoop.createRoot(); + await act(async () => { + root.render(); + await waitForPaint(['Initial']); + expect(root).toMatchRenderedOutput('Initial'); + }); + assertLog(['Final']); + expect(root).toMatchRenderedOutput('Final'); + }); + + // @gate enableUseDeferredValueInitialArg + it('defers during initial render when initialValue is provided, even if render is not sync', async () => { + function App() { + const value = useDeferredValue('Final', 'Initial'); + return ; + } + + const root = ReactNoop.createRoot(); + await act(async () => { + // Initial mount is a transition, but it should defer anyway + startTransition(() => root.render()); + await waitForPaint(['Initial']); + expect(root).toMatchRenderedOutput('Initial'); + }); + assertLog(['Final']); + expect(root).toMatchRenderedOutput('Final'); + }); }); diff --git a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js index db67dde0d7e1..89d150ed0988 100644 --- a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js +++ b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js @@ -3584,9 +3584,7 @@ describe('ReactHooksWithNoopRenderer', () => { let _setText; function App() { const [text, setText] = useState('A'); - const deferredText = useDeferredValue(text, { - timeoutMs: 500, - }); + const deferredText = useDeferredValue(text); _setText = setText; return ( <> diff --git a/packages/react-server/src/ReactFizzHooks.js b/packages/react-server/src/ReactFizzHooks.js index 3ecf1baa50a7..63d1327b8596 100644 --- a/packages/react-server/src/ReactFizzHooks.js +++ b/packages/react-server/src/ReactFizzHooks.js @@ -35,6 +35,7 @@ import { enableUseMemoCacheHook, enableAsyncActions, enableFormActions, + enableUseDeferredValueInitialArg, } from 'shared/ReactFeatureFlags'; import is from 'shared/objectIs'; import { @@ -555,7 +556,11 @@ function useSyncExternalStore( function useDeferredValue(value: T, initialValue?: T): T { resolveCurrentlyRenderingComponent(); - return value; + if (enableUseDeferredValueInitialArg) { + return initialValue !== undefined ? initialValue : value; + } else { + return value; + } } function unsupportedStartTransition() {