diff --git a/packages/react-reconciler/src/ReactFiberHooks.js b/packages/react-reconciler/src/ReactFiberHooks.js index 97f50d65f46c8..24746bde8d330 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.js +++ b/packages/react-reconciler/src/ReactFiberHooks.js @@ -1817,9 +1817,9 @@ function updateOptimisticImpl( // as an argument. It's called a passthrough because if there are no pending // updates, it will be returned as-is. // - // Reset the base state and memoized state to the passthrough. Future - // updates will be applied on top of this. - hook.baseState = hook.memoizedState = passthrough; + // Reset the base state to the passthrough. Future updates will be applied + // on top of this. + hook.baseState = passthrough; // If a reducer is not provided, default to the same one used by useState. const resolvedReducer: (S, A) => S = @@ -1853,9 +1853,9 @@ function rerenderOptimistic( // This is a mount. No updates to process. - // Reset the base state and memoized state to the passthrough. Future - // updates will be applied on top of this. - hook.baseState = hook.memoizedState = passthrough; + // Reset the base state to the passthrough. Future updates will be applied + // on top of this. + hook.baseState = passthrough; const dispatch = hook.queue.dispatch; return [passthrough, dispatch]; } diff --git a/packages/react-reconciler/src/__tests__/ReactAsyncActions-test.js b/packages/react-reconciler/src/__tests__/ReactAsyncActions-test.js index a333c4c486b4a..13143fa5ecf5b 100644 --- a/packages/react-reconciler/src/__tests__/ReactAsyncActions-test.js +++ b/packages/react-reconciler/src/__tests__/ReactAsyncActions-test.js @@ -1112,4 +1112,67 @@ describe('ReactAsyncActions', () => { , ); }); + + // @gate enableAsyncActions + test('useOptimistic can update repeatedly in the same async action', async () => { + let startTransition; + let setLoadingProgress; + let setText; + function App() { + const [, _startTransition] = useTransition(); + const [text, _setText] = useState('A'); + const [loadingProgress, _setLoadingProgress] = useOptimistic(0); + startTransition = _startTransition; + setText = _setText; + setLoadingProgress = _setLoadingProgress; + + return ( + <> + {loadingProgress !== 0 ? ( +
+ +
+ ) : null} +
+ +
+ + ); + } + + // Initial render + const root = ReactNoop.createRoot(); + await act(() => root.render()); + assertLog(['A']); + expect(root).toMatchRenderedOutput(
A
); + + await act(async () => { + startTransition(async () => { + setLoadingProgress('25%'); + await getText('Wait 1'); + setLoadingProgress('75%'); + await getText('Wait 2'); + startTransition(() => setText('B')); + }); + }); + assertLog(['Loading... (25%)', 'A']); + expect(root).toMatchRenderedOutput( + <> +
Loading... (25%)
+
A
+ , + ); + + await act(() => resolveText('Wait 1')); + assertLog(['Loading... (75%)', 'A']); + expect(root).toMatchRenderedOutput( + <> +
Loading... (75%)
+
A
+ , + ); + + await act(() => resolveText('Wait 2')); + assertLog(['B']); + }); });