Skip to content

Commit ee037cb

Browse files
committed
suspense boundary deleted
1 parent 5eb152f commit ee037cb

File tree

3 files changed

+313
-4
lines changed

3 files changed

+313
-4
lines changed

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

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1998,6 +1998,33 @@ function commitDeletionEffectsOnFiber(
19981998
const markers = instance.pendingMarkers;
19991999
if (markers !== null) {
20002000
markers.forEach(marker => {
2001+
if (marker.deletions === null) {
2002+
marker.deletions = [];
2003+
2004+
if (marker.name !== null) {
2005+
addMarkerIncompleteCallbackToPendingTransition(
2006+
marker.name,
2007+
instance.transitions,
2008+
marker.deletions,
2009+
);
2010+
}
2011+
}
2012+
2013+
let name = null;
2014+
const parent = deletedFiber.return;
2015+
if (
2016+
parent !== null &&
2017+
parent.tag === SuspenseComponent &&
2018+
parent.memoizedProps.unstable_name
2019+
) {
2020+
name = parent.memoizedProps.unstable_name;
2021+
}
2022+
marker.deletions.push({
2023+
type: 'suspense',
2024+
name,
2025+
transitions: instance.transitions,
2026+
});
2027+
20012028
if (marker.pendingBoundaries.has(instance)) {
20022029
marker.pendingBoundaries.delete(instance);
20032030
}
@@ -3057,6 +3084,14 @@ function commitOffscreenPassiveMountEffects(
30573084
}
30583085

30593086
commitTransitionProgress(finishedWork);
3087+
3088+
if (!isHidden) {
3089+
instance.transitions = null;
3090+
instance.pendingMarkers = null;
3091+
instance.deletions = null;
3092+
instance.parents = null;
3093+
instance.name = null;
3094+
}
30603095
}
30613096
}
30623097

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,13 @@ function getFilteredDeletion(deletion: TransitionDeletion, endTime: number) {
170170
endTime,
171171
};
172172
}
173+
case 'suspense': {
174+
return {
175+
type: deletion.type,
176+
name: deletion.name,
177+
endTime,
178+
};
179+
}
173180
default: {
174181
return null;
175182
}

packages/react-reconciler/src/__tests__/ReactTransitionTracing-test.js

Lines changed: 271 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1531,8 +1531,8 @@ describe('ReactInteractionTracing', () => {
15311531
'Loading...',
15321532
'Suspend [Sibling Text]',
15331533
'Sibling Loading...',
1534-
'onMarkerIncomplete(transition one, marker one, 1000, [{endTime: 3000, name: marker one, type: marker}])',
1535-
'onMarkerIncomplete(transition one, parent, 1000, [{endTime: 3000, name: marker one, type: marker}])',
1534+
'onMarkerIncomplete(transition one, marker one, 1000, [{endTime: 3000, name: marker one, type: marker}, {endTime: 3000, name: suspense page, type: suspense}])',
1535+
'onMarkerIncomplete(transition one, parent, 1000, [{endTime: 3000, name: marker one, type: marker}, {endTime: 3000, name: suspense page, type: suspense}])',
15361536
]);
15371537

15381538
root.render(<App navigate={true} showMarker={true} />);
@@ -1679,8 +1679,8 @@ describe('ReactInteractionTracing', () => {
16791679
expect(Scheduler).toFlushAndYield([
16801680
'Suspend [Page Two]',
16811681
'Loading Two...',
1682-
'onMarkerIncomplete(transition, one, 1000, [{endTime: 3000, name: one, type: marker}])',
1683-
'onMarkerIncomplete(transition, parent, 1000, [{endTime: 3000, name: one, type: marker}])',
1682+
'onMarkerIncomplete(transition, one, 1000, [{endTime: 3000, name: one, type: marker}, {endTime: 3000, name: suspense one, type: suspense}])',
1683+
'onMarkerIncomplete(transition, parent, 1000, [{endTime: 3000, name: one, type: marker}, {endTime: 3000, name: suspense one, type: suspense}])',
16841684
]);
16851685

16861686
await resolveText('Page Two');
@@ -1698,6 +1698,273 @@ describe('ReactInteractionTracing', () => {
16981698
});
16991699
});
17001700

1701+
it('Suspense boundary added by the transition is deleted', async () => {
1702+
const transitionCallbacks = {
1703+
onTransitionStart: (name, startTime) => {
1704+
Scheduler.unstable_yieldValue(
1705+
`onTransitionStart(${name}, ${startTime})`,
1706+
);
1707+
},
1708+
onTransitionProgress: (name, startTime, endTime, pending) => {
1709+
const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
1710+
Scheduler.unstable_yieldValue(
1711+
`onTransitionProgress(${name}, ${startTime}, ${endTime}, [${suspenseNames}])`,
1712+
);
1713+
},
1714+
onTransitionComplete: (name, startTime, endTime) => {
1715+
Scheduler.unstable_yieldValue(
1716+
`onTransitionComplete(${name}, ${startTime}, ${endTime})`,
1717+
);
1718+
},
1719+
onMarkerProgress: (
1720+
transitioName,
1721+
markerName,
1722+
startTime,
1723+
currentTime,
1724+
pending,
1725+
) => {
1726+
const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
1727+
Scheduler.unstable_yieldValue(
1728+
`onMarkerProgress(${transitioName}, ${markerName}, ${startTime}, ${currentTime}, [${suspenseNames}])`,
1729+
);
1730+
},
1731+
onMarkerIncomplete: (
1732+
transitionName,
1733+
markerName,
1734+
startTime,
1735+
deletions,
1736+
) => {
1737+
Scheduler.unstable_yieldValue(
1738+
`onMarkerIncomplete(${transitionName}, ${markerName}, ${startTime}, [${stringifyDeletions(
1739+
deletions,
1740+
)}])`,
1741+
);
1742+
},
1743+
onMarkerComplete: (transitioName, markerName, startTime, endTime) => {
1744+
Scheduler.unstable_yieldValue(
1745+
`onMarkerComplete(${transitioName}, ${markerName}, ${startTime}, ${endTime})`,
1746+
);
1747+
},
1748+
};
1749+
1750+
function App({navigate, deleteOne}) {
1751+
return (
1752+
<div>
1753+
{navigate ? (
1754+
<React.unstable_TracingMarker name="parent">
1755+
<React.unstable_TracingMarker name="one">
1756+
{!deleteOne ? (
1757+
<Suspense
1758+
unstable_name="suspense one"
1759+
fallback={<Text text="Loading One..." />}>
1760+
<AsyncText text="Page One" />
1761+
<React.unstable_TracingMarker name="page one" />
1762+
<Suspense
1763+
unstable_name="suspense child"
1764+
fallback={<Text text="Loading Child..." />}>
1765+
<React.unstable_TracingMarker name="child" />
1766+
<AsyncText text="Child" />
1767+
</Suspense>
1768+
</Suspense>
1769+
) : null}
1770+
</React.unstable_TracingMarker>
1771+
<React.unstable_TracingMarker name="two">
1772+
<Suspense
1773+
unstable_name="suspense two"
1774+
fallback={<Text text="Loading Two..." />}>
1775+
<AsyncText text="Page Two" />
1776+
</Suspense>
1777+
</React.unstable_TracingMarker>
1778+
</React.unstable_TracingMarker>
1779+
) : (
1780+
<Text text="Page One" />
1781+
)}
1782+
</div>
1783+
);
1784+
}
1785+
const root = ReactNoop.createRoot({
1786+
unstable_transitionCallbacks: transitionCallbacks,
1787+
});
1788+
await act(async () => {
1789+
root.render(<App navigate={false} deleteOne={false} />);
1790+
1791+
ReactNoop.expire(1000);
1792+
await advanceTimers(1000);
1793+
expect(Scheduler).toFlushAndYield(['Page One']);
1794+
1795+
startTransition(
1796+
() => root.render(<App navigate={true} deleteOne={false} />),
1797+
{
1798+
name: 'transition',
1799+
},
1800+
);
1801+
ReactNoop.expire(1000);
1802+
await advanceTimers(1000);
1803+
expect(Scheduler).toFlushAndYield([
1804+
'Suspend [Page One]',
1805+
'Suspend [Child]',
1806+
'Loading Child...',
1807+
'Loading One...',
1808+
'Suspend [Page Two]',
1809+
'Loading Two...',
1810+
'onTransitionStart(transition, 1000)',
1811+
'onMarkerProgress(transition, parent, 1000, 2000, [suspense one, suspense two])',
1812+
'onMarkerProgress(transition, one, 1000, 2000, [suspense one])',
1813+
'onMarkerProgress(transition, two, 1000, 2000, [suspense two])',
1814+
'onTransitionProgress(transition, 1000, 2000, [suspense one, suspense two])',
1815+
]);
1816+
1817+
await resolveText('Page One');
1818+
ReactNoop.expire(1000);
1819+
await advanceTimers(1000);
1820+
expect(Scheduler).toFlushAndYield([
1821+
'Page One',
1822+
'Suspend [Child]',
1823+
'Loading Child...',
1824+
'onMarkerProgress(transition, parent, 1000, 3000, [suspense two, suspense child])',
1825+
'onMarkerProgress(transition, one, 1000, 3000, [suspense child])',
1826+
'onMarkerComplete(transition, page one, 1000, 3000)',
1827+
'onTransitionProgress(transition, 1000, 3000, [suspense two, suspense child])',
1828+
]);
1829+
1830+
root.render(<App navigate={true} deleteOne={true} />);
1831+
ReactNoop.expire(1000);
1832+
await advanceTimers(1000);
1833+
expect(Scheduler).toFlushAndYield([
1834+
'Suspend [Page Two]',
1835+
'Loading Two...',
1836+
// "suspense one" has unsuspended so shouldn't be included
1837+
// tracing marker "page one" has completed so shouldn't be included
1838+
// all children of "suspense child" haven't yet been rendered so shouldn't be included
1839+
'onMarkerIncomplete(transition, parent, 1000, [{endTime: 4000, name: suspense child, type: suspense}])',
1840+
'onMarkerIncomplete(transition, one, 1000, [{endTime: 4000, name: suspense child, type: suspense}])',
1841+
]);
1842+
1843+
await resolveText('Page Two');
1844+
ReactNoop.expire(1000);
1845+
await advanceTimers(1000);
1846+
expect(Scheduler).toFlushAndYield([
1847+
'Page Two',
1848+
'onMarkerProgress(transition, parent, 1000, 5000, [])',
1849+
'onMarkerProgress(transition, two, 1000, 5000, [])',
1850+
'onMarkerComplete(transition, two, 1000, 5000)',
1851+
'onTransitionProgress(transition, 1000, 5000, [])',
1852+
]);
1853+
});
1854+
});
1855+
1856+
it('Suspense boundary not added by the transition is deleted ', async () => {
1857+
const transitionCallbacks = {
1858+
onTransitionStart: (name, startTime) => {
1859+
Scheduler.unstable_yieldValue(
1860+
`onTransitionStart(${name}, ${startTime})`,
1861+
);
1862+
},
1863+
onTransitionProgress: (name, startTime, endTime, pending) => {
1864+
const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
1865+
Scheduler.unstable_yieldValue(
1866+
`onTransitionProgress(${name}, ${startTime}, ${endTime}, [${suspenseNames}])`,
1867+
);
1868+
},
1869+
onTransitionComplete: (name, startTime, endTime) => {
1870+
Scheduler.unstable_yieldValue(
1871+
`onTransitionComplete(${name}, ${startTime}, ${endTime})`,
1872+
);
1873+
},
1874+
onMarkerProgress: (
1875+
transitioName,
1876+
markerName,
1877+
startTime,
1878+
currentTime,
1879+
pending,
1880+
) => {
1881+
const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
1882+
Scheduler.unstable_yieldValue(
1883+
`onMarkerProgress(${transitioName}, ${markerName}, ${startTime}, ${currentTime}, [${suspenseNames}])`,
1884+
);
1885+
},
1886+
onMarkerIncomplete: (
1887+
transitionName,
1888+
markerName,
1889+
startTime,
1890+
deletions,
1891+
) => {
1892+
Scheduler.unstable_yieldValue(
1893+
`onMarkerIncomplete(${transitionName}, ${markerName}, ${startTime}, [${stringifyDeletions(
1894+
deletions,
1895+
)}])`,
1896+
);
1897+
},
1898+
onMarkerComplete: (transitioName, markerName, startTime, endTime) => {
1899+
Scheduler.unstable_yieldValue(
1900+
`onMarkerComplete(${transitioName}, ${markerName}, ${startTime}, ${endTime})`,
1901+
);
1902+
},
1903+
};
1904+
1905+
function App({show}) {
1906+
return (
1907+
<React.unstable_TracingMarker name="parent">
1908+
{show ? (
1909+
<Suspense unstable_name="appended child">
1910+
<AsyncText text="Appended child" />
1911+
</Suspense>
1912+
) : null}
1913+
<Suspense unstable_name="child">
1914+
<AsyncText text="Child" />
1915+
</Suspense>
1916+
</React.unstable_TracingMarker>
1917+
);
1918+
}
1919+
1920+
const root = ReactNoop.createRoot({
1921+
unstable_transitionCallbacks: transitionCallbacks,
1922+
});
1923+
await act(async () => {
1924+
startTransition(() => root.render(<App show={false} />), {
1925+
name: 'transition',
1926+
});
1927+
ReactNoop.expire(1000);
1928+
await advanceTimers(1000);
1929+
1930+
expect(Scheduler).toFlushAndYield([
1931+
'Suspend [Child]',
1932+
'onTransitionStart(transition, 0)',
1933+
'onMarkerProgress(transition, parent, 0, 1000, [child])',
1934+
'onTransitionProgress(transition, 0, 1000, [child])',
1935+
]);
1936+
1937+
root.render(<App show={true} />);
1938+
ReactNoop.expire(1000);
1939+
await advanceTimers(1000);
1940+
// This appended child isn't part of the transition so we
1941+
// don't call any callback
1942+
expect(Scheduler).toFlushAndYield([
1943+
'Suspend [Appended child]',
1944+
'Suspend [Child]',
1945+
]);
1946+
1947+
// This deleted child isn't part of the transition so we
1948+
// don't call any callbacks
1949+
root.render(<App show={false} />);
1950+
ReactNoop.expire(1000);
1951+
await advanceTimers(1000);
1952+
expect(Scheduler).toFlushAndYield(['Suspend [Child]']);
1953+
1954+
await resolveText('Child');
1955+
ReactNoop.expire(1000);
1956+
await advanceTimers(1000);
1957+
1958+
expect(Scheduler).toFlushAndYield([
1959+
'Child',
1960+
'onMarkerProgress(transition, parent, 0, 4000, [])',
1961+
'onMarkerComplete(transition, parent, 0, 4000)',
1962+
'onTransitionProgress(transition, 0, 4000, [])',
1963+
'onTransitionComplete(transition, 0, 4000)',
1964+
]);
1965+
});
1966+
});
1967+
17011968
// @gate enableTransitionTracing
17021969
it('warns when marker name changes', async () => {
17031970
const transitionCallbacks = {

0 commit comments

Comments
 (0)