Skip to content

Commit c7101a2

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

File tree

3 files changed

+315
-4
lines changed

3 files changed

+315
-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: 273 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,275 @@ describe('ReactInteractionTracing', () => {
16981698
});
16991699
});
17001700

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

0 commit comments

Comments
 (0)