Skip to content

Commit f47e471

Browse files
committed
Revert "Fix infinite update loop that happens when an unmemoized value is passed to useDeferredValue (#24247)"
This reverts commit f993ffc.
1 parent 9ae80d6 commit f47e471

File tree

5 files changed

+68
-361
lines changed

5 files changed

+68
-361
lines changed

packages/react-devtools-shared/src/__tests__/preprocessData-test.js

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1463,9 +1463,7 @@ describe('Timeline profiler', () => {
14631463
expect(event.warning).toBe(null);
14641464
});
14651465

1466-
// This is temporarily disabled because the warning doesn't work
1467-
// with useDeferredValue
1468-
it.skip('should warn about long nested (state) updates during layout effects', async () => {
1466+
it('should warn about long nested (state) updates during layout effects', async () => {
14691467
function Component() {
14701468
const [didMount, setDidMount] = React.useState(false);
14711469
Scheduler.unstable_yieldValue(
@@ -1525,9 +1523,7 @@ describe('Timeline profiler', () => {
15251523
);
15261524
});
15271525

1528-
// This is temporarily disabled because the warning doesn't work
1529-
// with useDeferredValue
1530-
it.skip('should warn about long nested (forced) updates during layout effects', async () => {
1526+
it('should warn about long nested (forced) updates during layout effects', async () => {
15311527
class Component extends React.Component {
15321528
_didMount: boolean = false;
15331529
componentDidMount() {
@@ -1658,9 +1654,7 @@ describe('Timeline profiler', () => {
16581654
});
16591655
});
16601656

1661-
// This is temporarily disabled because the warning doesn't work
1662-
// with useDeferredValue
1663-
it.skip('should not warn about deferred value updates scheduled during commit phase', async () => {
1657+
it('should not warn about deferred value updates scheduled during commit phase', async () => {
16641658
function Component() {
16651659
const [value, setValue] = React.useState(0);
16661660
const deferredValue = React.useDeferredValue(value);

packages/react-devtools-timeline/src/import-worker/preprocessData.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1124,10 +1124,7 @@ export default async function preprocessData(
11241124
lane => profilerData.laneToLabelMap.get(lane) === 'Transition',
11251125
)
11261126
) {
1127-
// FIXME: This warning doesn't account for "nested updates" that are
1128-
// spawned by useDeferredValue. Disabling temporarily until we figure
1129-
// out the right way to handle this.
1130-
// schedulingEvent.warning = WARNING_STRINGS.NESTED_UPDATE;
1127+
schedulingEvent.warning = WARNING_STRINGS.NESTED_UPDATE;
11311128
}
11321129
}
11331130
});

packages/react-reconciler/src/ReactFiberHooks.new.js

Lines changed: 32 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,6 @@ import {
4747
NoLanes,
4848
isSubsetOfLanes,
4949
includesBlockingLane,
50-
includesOnlyNonUrgentLanes,
51-
claimNextTransitionLane,
5250
mergeLanes,
5351
removeLanes,
5452
intersectLanes,
@@ -1928,73 +1926,45 @@ function updateMemo<T>(
19281926
}
19291927

19301928
function mountDeferredValue<T>(value: T): T {
1931-
const hook = mountWorkInProgressHook();
1932-
hook.memoizedState = value;
1933-
return value;
1929+
const [prevValue, setValue] = mountState(value);
1930+
mountEffect(() => {
1931+
const prevTransition = ReactCurrentBatchConfig.transition;
1932+
ReactCurrentBatchConfig.transition = {};
1933+
try {
1934+
setValue(value);
1935+
} finally {
1936+
ReactCurrentBatchConfig.transition = prevTransition;
1937+
}
1938+
}, [value]);
1939+
return prevValue;
19341940
}
19351941

19361942
function updateDeferredValue<T>(value: T): T {
1937-
const hook = updateWorkInProgressHook();
1938-
const resolvedCurrentHook: Hook = (currentHook: any);
1939-
const prevValue: T = resolvedCurrentHook.memoizedState;
1940-
return updateDeferredValueImpl(hook, prevValue, value);
1943+
const [prevValue, setValue] = updateState(value);
1944+
updateEffect(() => {
1945+
const prevTransition = ReactCurrentBatchConfig.transition;
1946+
ReactCurrentBatchConfig.transition = {};
1947+
try {
1948+
setValue(value);
1949+
} finally {
1950+
ReactCurrentBatchConfig.transition = prevTransition;
1951+
}
1952+
}, [value]);
1953+
return prevValue;
19411954
}
19421955

19431956
function rerenderDeferredValue<T>(value: T): T {
1944-
const hook = updateWorkInProgressHook();
1945-
if (currentHook === null) {
1946-
// This is a rerender during a mount.
1947-
hook.memoizedState = value;
1948-
return value;
1949-
} else {
1950-
// This is a rerender during an update.
1951-
const prevValue: T = currentHook.memoizedState;
1952-
return updateDeferredValueImpl(hook, prevValue, value);
1953-
}
1954-
}
1955-
1956-
function updateDeferredValueImpl<T>(hook: Hook, prevValue: T, value: T): T {
1957-
const shouldDeferValue = !includesOnlyNonUrgentLanes(renderLanes);
1958-
if (shouldDeferValue) {
1959-
// This is an urgent update. If the value has changed, keep using the
1960-
// previous value and spawn a deferred render to update it later.
1961-
1962-
if (!is(value, prevValue)) {
1963-
// Schedule a deferred render
1964-
const deferredLane = claimNextTransitionLane();
1965-
currentlyRenderingFiber.lanes = mergeLanes(
1966-
currentlyRenderingFiber.lanes,
1967-
deferredLane,
1968-
);
1969-
markSkippedUpdateLanes(deferredLane);
1970-
1971-
// Set this to true to indicate that the rendered value is inconsistent
1972-
// from the latest value. The name "baseState" doesn't really match how we
1973-
// use it because we're reusing a state hook field instead of creating a
1974-
// new one.
1975-
hook.baseState = true;
1957+
const [prevValue, setValue] = rerenderState(value);
1958+
updateEffect(() => {
1959+
const prevTransition = ReactCurrentBatchConfig.transition;
1960+
ReactCurrentBatchConfig.transition = {};
1961+
try {
1962+
setValue(value);
1963+
} finally {
1964+
ReactCurrentBatchConfig.transition = prevTransition;
19761965
}
1977-
1978-
// Reuse the previous value
1979-
return prevValue;
1980-
} else {
1981-
// This is not an urgent update, so we can use the latest value regardless
1982-
// of what it is. No need to defer it.
1983-
1984-
// However, if we're currently inside a spawned render, then we need to mark
1985-
// this as an update to prevent the fiber from bailing out.
1986-
//
1987-
// `baseState` is true when the current value is different from the rendered
1988-
// value. The name doesn't really match how we use it because we're reusing
1989-
// a state hook field instead of creating a new one.
1990-
if (hook.baseState) {
1991-
// Flip this back to false.
1992-
hook.baseState = false;
1993-
markWorkInProgressReceivedUpdate();
1994-
}
1995-
1996-
return value;
1997-
}
1966+
}, [value]);
1967+
return prevValue;
19981968
}
19991969

20001970
function startTransition(setPending, callback, options) {

packages/react-reconciler/src/ReactFiberHooks.old.js

Lines changed: 32 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,6 @@ import {
4747
NoLanes,
4848
isSubsetOfLanes,
4949
includesBlockingLane,
50-
includesOnlyNonUrgentLanes,
51-
claimNextTransitionLane,
5250
mergeLanes,
5351
removeLanes,
5452
intersectLanes,
@@ -1928,73 +1926,45 @@ function updateMemo<T>(
19281926
}
19291927

19301928
function mountDeferredValue<T>(value: T): T {
1931-
const hook = mountWorkInProgressHook();
1932-
hook.memoizedState = value;
1933-
return value;
1929+
const [prevValue, setValue] = mountState(value);
1930+
mountEffect(() => {
1931+
const prevTransition = ReactCurrentBatchConfig.transition;
1932+
ReactCurrentBatchConfig.transition = {};
1933+
try {
1934+
setValue(value);
1935+
} finally {
1936+
ReactCurrentBatchConfig.transition = prevTransition;
1937+
}
1938+
}, [value]);
1939+
return prevValue;
19341940
}
19351941

19361942
function updateDeferredValue<T>(value: T): T {
1937-
const hook = updateWorkInProgressHook();
1938-
const resolvedCurrentHook: Hook = (currentHook: any);
1939-
const prevValue: T = resolvedCurrentHook.memoizedState;
1940-
return updateDeferredValueImpl(hook, prevValue, value);
1943+
const [prevValue, setValue] = updateState(value);
1944+
updateEffect(() => {
1945+
const prevTransition = ReactCurrentBatchConfig.transition;
1946+
ReactCurrentBatchConfig.transition = {};
1947+
try {
1948+
setValue(value);
1949+
} finally {
1950+
ReactCurrentBatchConfig.transition = prevTransition;
1951+
}
1952+
}, [value]);
1953+
return prevValue;
19411954
}
19421955

19431956
function rerenderDeferredValue<T>(value: T): T {
1944-
const hook = updateWorkInProgressHook();
1945-
if (currentHook === null) {
1946-
// This is a rerender during a mount.
1947-
hook.memoizedState = value;
1948-
return value;
1949-
} else {
1950-
// This is a rerender during an update.
1951-
const prevValue: T = currentHook.memoizedState;
1952-
return updateDeferredValueImpl(hook, prevValue, value);
1953-
}
1954-
}
1955-
1956-
function updateDeferredValueImpl<T>(hook: Hook, prevValue: T, value: T): T {
1957-
const shouldDeferValue = !includesOnlyNonUrgentLanes(renderLanes);
1958-
if (shouldDeferValue) {
1959-
// This is an urgent update. If the value has changed, keep using the
1960-
// previous value and spawn a deferred render to update it later.
1961-
1962-
if (!is(value, prevValue)) {
1963-
// Schedule a deferred render
1964-
const deferredLane = claimNextTransitionLane();
1965-
currentlyRenderingFiber.lanes = mergeLanes(
1966-
currentlyRenderingFiber.lanes,
1967-
deferredLane,
1968-
);
1969-
markSkippedUpdateLanes(deferredLane);
1970-
1971-
// Set this to true to indicate that the rendered value is inconsistent
1972-
// from the latest value. The name "baseState" doesn't really match how we
1973-
// use it because we're reusing a state hook field instead of creating a
1974-
// new one.
1975-
hook.baseState = true;
1957+
const [prevValue, setValue] = rerenderState(value);
1958+
updateEffect(() => {
1959+
const prevTransition = ReactCurrentBatchConfig.transition;
1960+
ReactCurrentBatchConfig.transition = {};
1961+
try {
1962+
setValue(value);
1963+
} finally {
1964+
ReactCurrentBatchConfig.transition = prevTransition;
19761965
}
1977-
1978-
// Reuse the previous value
1979-
return prevValue;
1980-
} else {
1981-
// This is not an urgent update, so we can use the latest value regardless
1982-
// of what it is. No need to defer it.
1983-
1984-
// However, if we're currently inside a spawned render, then we need to mark
1985-
// this as an update to prevent the fiber from bailing out.
1986-
//
1987-
// `baseState` is true when the current value is different from the rendered
1988-
// value. The name doesn't really match how we use it because we're reusing
1989-
// a state hook field instead of creating a new one.
1990-
if (hook.baseState) {
1991-
// Flip this back to false.
1992-
hook.baseState = false;
1993-
markWorkInProgressReceivedUpdate();
1994-
}
1995-
1996-
return value;
1997-
}
1966+
}, [value]);
1967+
return prevValue;
19981968
}
19991969

20001970
function startTransition(setPending, callback, options) {

0 commit comments

Comments
 (0)