mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Fix: useOptimistic should return passthrough value when there are no updates pending (#27936)
This fixes a bug that happened when the canonical value passed to useOptimistic without an accompanying call to setOptimistic. In this scenario, useOptimistic should pass through the new canonical value. I had written tests for the more complicated scenario, where a new value is passed while there are still pending optimistic updates, but not this simpler one.
This commit is contained in:
+11
-2
@@ -1256,10 +1256,19 @@ function updateReducerImpl<S, A>(
|
||||
queue.pending = null;
|
||||
}
|
||||
|
||||
if (baseQueue !== null) {
|
||||
const baseState = hook.baseState;
|
||||
if (baseQueue === null) {
|
||||
// If there are no pending updates, then the memoized state should be the
|
||||
// same as the base state. Currently these only diverge in the case of
|
||||
// useOptimistic, because useOptimistic accepts a new baseState on
|
||||
// every render.
|
||||
hook.memoizedState = baseState;
|
||||
// We don't need to call markWorkInProgressReceivedUpdate because
|
||||
// baseState is derived from other reactive values.
|
||||
} else {
|
||||
// We have a queue to process.
|
||||
const first = baseQueue.next;
|
||||
let newState = hook.baseState;
|
||||
let newState = baseState;
|
||||
|
||||
let newBaseState = null;
|
||||
let newBaseQueueFirst = null;
|
||||
|
||||
@@ -818,6 +818,52 @@ describe('ReactAsyncActions', () => {
|
||||
);
|
||||
});
|
||||
|
||||
// @gate enableAsyncActions
|
||||
test(
|
||||
'regression: when there are no pending transitions, useOptimistic should ' +
|
||||
'always return the passthrough value',
|
||||
async () => {
|
||||
let setCanonicalState;
|
||||
function App() {
|
||||
const [canonicalState, _setCanonicalState] = useState(0);
|
||||
const [optimisticState] = useOptimistic(canonicalState);
|
||||
setCanonicalState = _setCanonicalState;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<Text text={'Canonical: ' + canonicalState} />
|
||||
</div>
|
||||
<div>
|
||||
<Text text={'Optimistic: ' + optimisticState} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const root = ReactNoop.createRoot();
|
||||
await act(() => root.render(<App />));
|
||||
assertLog(['Canonical: 0', 'Optimistic: 0']);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
<div>Canonical: 0</div>
|
||||
<div>Optimistic: 0</div>
|
||||
</>,
|
||||
);
|
||||
|
||||
// Update the canonical state. The optimistic state should update, too,
|
||||
// even though there was no transition, and no call to setOptimisticState.
|
||||
await act(() => setCanonicalState(1));
|
||||
assertLog(['Canonical: 1', 'Optimistic: 1']);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
<div>Canonical: 1</div>
|
||||
<div>Optimistic: 1</div>
|
||||
</>,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// @gate enableAsyncActions
|
||||
test('regression: useOptimistic during setState-in-render', async () => {
|
||||
// This is a regression test for a very specific case where useOptimistic is
|
||||
|
||||
Reference in New Issue
Block a user