Skip to content

Commit 26cf280

Browse files
authored
Switch the default revealOrder to "forwards" and tail "hidden" on SuspenseList (#35018)
We have warned about this for a while now so we can make the switch. Often when you reach for SuspenseList, you mean forwards. It doesn't make sense to have the default to just be a noop. While "together" is another useful mode that's more like a Group so isn't so associated with the default as List. So we're switching it. However, tail=hidden isn't as obvious of a default it does allow for a convenient pattern for streaming in list of items by default. This doesn't yet switch the rendering order of "backwards". That's coming in a follow up.
1 parent c9ddee7 commit 26cf280

7 files changed

Lines changed: 142 additions & 125 deletions

File tree

‎packages/react-dom/src/__tests__/ReactDOMFizzSuspenseList-test.js‎

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ describe('ReactDOMFizzSuspenseList', () => {
134134
}
135135

136136
// @gate enableSuspenseList
137-
it('shows content independently by default',async()=>{
137+
it('shows content forwards by default',async()=>{
138138
constA=createAsyncText('A');
139139
constB=createAsyncText('B');
140140
constC=createAsyncText('C');
@@ -157,31 +157,38 @@ describe('ReactDOMFizzSuspenseList', () => {
157157
);
158158
}
159159

160-
awaitA.resolve();
160+
awaitC.resolve();
161161

162162
awaitserverAct(async()=>{
163163
const{pipe}=ReactDOMFizzServer.renderToPipeableStream(<Foo/>);
164164
pipe(writable);
165165
});
166166

167-
assertLog(['A','Suspend! [B]','Suspend! [C]','Loading B','Loading C']);
167+
assertLog([
168+
'Suspend! [A]',
169+
'Suspend! [B]',// TODO: Defer rendering the content after fallback if previous suspended,
170+
'C',
171+
'Loading A',
172+
'Loading B',
173+
'Loading C',
174+
]);
168175

169176
expect(getVisibleChildren(container)).toEqual(
170177
<div>
171-
<span>A</span>
178+
<span>Loading A</span>
172179
<span>Loading B</span>
173180
<span>Loading C</span>
174181
</div>,
175182
);
176183

177-
awaitserverAct(()=>C.resolve());
178-
assertLog(['C']);
184+
awaitserverAct(()=>A.resolve());
185+
assertLog(['A']);
179186

180187
expect(getVisibleChildren(container)).toEqual(
181188
<div>
182189
<span>A</span>
183190
<span>Loading B</span>
184-
<span>C</span>
191+
<span>Loading C</span>
185192
</div>,
186193
);
187194

‎packages/react-reconciler/src/ReactChildFiber.js‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2104,7 +2104,8 @@ export function validateSuspenseListChildren(
21042104
){
21052105
if(__DEV__){
21062106
if(
2107-
(revealOrder==='forwards'||
2107+
(revealOrder==null||
2108+
revealOrder==='forwards'||
21082109
revealOrder==='backwards'||
21092110
revealOrder==='unstable_legacy-backwards')&&
21102111
children!==undefined&&

‎packages/react-reconciler/src/ReactFiberBeginWork.js‎

Lines changed: 34 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -3245,20 +3245,15 @@ function validateRevealOrder(revealOrder: SuspenseListRevealOrder) {
32453245
if(__DEV__){
32463246
constcacheKey=revealOrder==null ? 'null' : revealOrder;
32473247
if(
3248+
revealOrder!=null&&
32483249
revealOrder!=='forwards'&&
32493250
revealOrder!=='unstable_legacy-backwards'&&
32503251
revealOrder!=='together'&&
32513252
revealOrder!=='independent'&&
32523253
!didWarnAboutRevealOrder[cacheKey]
32533254
){
32543255
didWarnAboutRevealOrder[cacheKey]=true;
3255-
if(revealOrder==null){
3256-
console.error(
3257-
'The default for the <SuspenseList revealOrder="..."> prop is changing. '+
3258-
'To be future compatible you must explictly specify either '+
3259-
'"independent" (the current default), "together", "forwards" or "legacy_unstable-backwards".',
3260-
);
3261-
}elseif(revealOrder==='backwards'){
3256+
if(revealOrder==='backwards'){
32623257
console.error(
32633258
'The rendering order of <SuspenseList revealOrder="backwards"> is changing. '+
32643259
'To be future compatible you must specify revealOrder="legacy_unstable-backwards" instead.',
@@ -3314,18 +3309,7 @@ function validateTailOptions(
33143309
constcacheKey=tailMode==null ? 'null' : tailMode;
33153310
if(!didWarnAboutTailOptions[cacheKey]){
33163311
if(tailMode==null){
3317-
if(
3318-
revealOrder==='forwards'||
3319-
revealOrder==='backwards'||
3320-
revealOrder==='unstable_legacy-backwards'
3321-
){
3322-
didWarnAboutTailOptions[cacheKey]=true;
3323-
console.error(
3324-
'The default for the <SuspenseList tail="..."> prop is changing. '+
3325-
'To be future compatible you must explictly specify either '+
3326-
'"visible" (the current default), "collapsed" or "hidden".',
3327-
);
3328-
}
3312+
// The default tail is now "hidden".
33293313
}elseif(
33303314
tailMode!=='visible'&&
33313315
tailMode!=='collapsed'&&
@@ -3338,14 +3322,15 @@ function validateTailOptions(
33383322
tailMode,
33393323
);
33403324
}elseif(
3325+
revealOrder!=null&&
33413326
revealOrder!=='forwards'&&
33423327
revealOrder!=='backwards'&&
33433328
revealOrder!=='unstable_legacy-backwards'
33443329
){
33453330
didWarnAboutTailOptions[cacheKey]=true;
33463331
console.error(
33473332
'<SuspenseList tail="%s" /> is only valid if revealOrder is '+
3348-
'"forwards" or "backwards". '+
3333+
'"forwards" (default) or "backwards". '+
33493334
'Did you mean to specify revealOrder="forwards"?',
33503335
tailMode,
33513336
);
@@ -3449,30 +3434,6 @@ function updateSuspenseListComponent(
34493434
workInProgress.memoizedState=null;
34503435
}else{
34513436
switch(revealOrder){
3452-
case'forwards': {
3453-
constlastContentRow=findLastContentRow(workInProgress.child);
3454-
lettail;
3455-
if(lastContentRow===null){
3456-
// The whole list is part of the tail.
3457-
// TODO: We could fast path by just rendering the tail now.
3458-
tail=workInProgress.child;
3459-
workInProgress.child=null;
3460-
}else{
3461-
// Disconnect the tail rows after the content row.
3462-
// We're going to render them separately later.
3463-
tail=lastContentRow.sibling;
3464-
lastContentRow.sibling=null;
3465-
}
3466-
initSuspenseListRenderState(
3467-
workInProgress,
3468-
false,// isBackwards
3469-
tail,
3470-
lastContentRow,
3471-
tailMode,
3472-
treeForkCount,
3473-
);
3474-
break;
3475-
}
34763437
case'backwards':
34773438
case'unstable_legacy-backwards': {
34783439
// We're going to find the first row that has existing content.
@@ -3517,10 +3478,37 @@ function updateSuspenseListComponent(
35173478
);
35183479
break;
35193480
}
3520-
default: {
3521-
// The default reveal order is the same as not having
3481+
case'independent': {
3482+
// The "independent" reveal order is the same as not having
35223483
// a boundary.
35233484
workInProgress.memoizedState=null;
3485+
break;
3486+
}
3487+
// The default is now forwards.
3488+
case'forwards':
3489+
default: {
3490+
constlastContentRow=findLastContentRow(workInProgress.child);
3491+
lettail;
3492+
if(lastContentRow===null){
3493+
// The whole list is part of the tail.
3494+
// TODO: We could fast path by just rendering the tail now.
3495+
tail=workInProgress.child;
3496+
workInProgress.child=null;
3497+
}else{
3498+
// Disconnect the tail rows after the content row.
3499+
// We're going to render them separately later.
3500+
tail=lastContentRow.sibling;
3501+
lastContentRow.sibling=null;
3502+
}
3503+
initSuspenseListRenderState(
3504+
workInProgress,
3505+
false,// isBackwards
3506+
tail,
3507+
lastContentRow,
3508+
tailMode,
3509+
treeForkCount,
3510+
);
3511+
break;
35243512
}
35253513
}
35263514
}

‎packages/react-reconciler/src/ReactFiberCompleteWork.js‎

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -698,7 +698,11 @@ function cutOffTailIfNeeded(
698698
return;
699699
}
700700
switch(renderState.tailMode){
701-
case'hidden': {
701+
case'visible': {
702+
// Everything should remain as it was.
703+
break;
704+
}
705+
case'collapsed': {
702706
// Any insertions at the end of the tail list after this point
703707
// should be invisible. If there are already mounted boundaries
704708
// anything before them are not considered for collapsing.
@@ -716,15 +720,23 @@ function cutOffTailIfNeeded(
716720
// last rendered item.
717721
if(lastTailNode===null){
718722
// All remaining items in the tail are insertions.
719-
renderState.tail=null;
723+
if(!hasRenderedATailFallback&&renderState.tail!==null){
724+
// We suspended during the head. We want to show at least one
725+
// row at the tail. So we'll keep on and cut off the rest.
726+
renderState.tail.sibling=null;
727+
}else{
728+
renderState.tail=null;
729+
}
720730
}else{
721731
// Detach the insertion after the last node that was already
722732
// inserted.
723733
lastTailNode.sibling=null;
724734
}
725735
break;
726736
}
727-
case'collapsed': {
737+
// Hidden is now the default.
738+
case'hidden':
739+
default: {
728740
// Any insertions at the end of the tail list after this point
729741
// should be invisible. If there are already mounted boundaries
730742
// anything before them are not considered for collapsing.
@@ -742,13 +754,7 @@ function cutOffTailIfNeeded(
742754
// last rendered item.
743755
if(lastTailNode===null){
744756
// All remaining items in the tail are insertions.
745-
if(!hasRenderedATailFallback&&renderState.tail!==null){
746-
// We suspended during the head. We want to show at least one
747-
// row at the tail. So we'll keep on and cut off the rest.
748-
renderState.tail.sibling=null;
749-
}else{
750-
renderState.tail=null;
751-
}
757+
renderState.tail=null;
752758
}else{
753759
// Detach the insertion after the last node that was already
754760
// inserted.
@@ -1795,7 +1801,8 @@ function completeWork(
17951801
// This might have been modified.
17961802
if(
17971803
renderState.tail===null&&
1798-
renderState.tailMode==='hidden'&&
1804+
renderState.tailMode!=='collapsed'&&
1805+
renderState.tailMode!=='visible'&&
17991806
!renderedTail.alternate&&
18001807
!getIsHydrating()// We don't cut it if we're hydrating.
18011808
){

‎packages/react-reconciler/src/ReactFiberSuspenseComponent.js‎

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,7 @@ export function findFirstSuspended(row: Fiber): null | Fiber {
7979
node.tag===SuspenseListComponent&&
8080
// Independent revealOrder can't be trusted because it doesn't
8181
// keep track of whether it suspended or not.
82-
(node.memoizedProps.revealOrder==='forwards'||
83-
node.memoizedProps.revealOrder==='backwards'||
84-
node.memoizedProps.revealOrder==='unstable_legacy-backwards'||
85-
node.memoizedProps.revealOrder==='together')
82+
node.memoizedProps.revealOrder!=='independent'
8683
){
8784
constdidSuspend=(node.flags&DidCapture)!==NoFlags;
8885
if(didSuspend){

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Switch the default revealOrder to "forwards" and tail "hidden" on Sus… · react/react@26cf280 · GitHub
Skip to content

Commit 26cf280

Browse files
authored
Switch the default revealOrder to "forwards" and tail "hidden" on SuspenseList (#35018)
We have warned about this for a while now so we can make the switch. Often when you reach for SuspenseList, you mean forwards. It doesn't make sense to have the default to just be a noop. While "together" is another useful mode that's more like a Group so isn't so associated with the default as List. So we're switching it. However, tail=hidden isn't as obvious of a default it does allow for a convenient pattern for streaming in list of items by default. This doesn't yet switch the rendering order of "backwards". That's coming in a follow up.
1 parent c9ddee7 commit 26cf280

7 files changed

Lines changed: 142 additions & 125 deletions

File tree

‎packages/react-dom/src/__tests__/ReactDOMFizzSuspenseList-test.js‎

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ describe('ReactDOMFizzSuspenseList', () => {
134134
}
135135

136136
// @gate enableSuspenseList
137-
it('shows content independently by default',async()=>{
137+
it('shows content forwards by default',async()=>{
138138
constA=createAsyncText('A');
139139
constB=createAsyncText('B');
140140
constC=createAsyncText('C');
@@ -157,31 +157,38 @@ describe('ReactDOMFizzSuspenseList', () => {
157157
);
158158
}
159159

160-
awaitA.resolve();
160+
awaitC.resolve();
161161

162162
awaitserverAct(async()=>{
163163
const{pipe}=ReactDOMFizzServer.renderToPipeableStream(<Foo/>);
164164
pipe(writable);
165165
});
166166

167-
assertLog(['A','Suspend! [B]','Suspend! [C]','Loading B','Loading C']);
167+
assertLog([
168+
'Suspend! [A]',
169+
'Suspend! [B]',// TODO: Defer rendering the content after fallback if previous suspended,
170+
'C',
171+
'Loading A',
172+
'Loading B',
173+
'Loading C',
174+
]);
168175

169176
expect(getVisibleChildren(container)).toEqual(
170177
<div>
171-
<span>A</span>
178+
<span>Loading A</span>
172179
<span>Loading B</span>
173180
<span>Loading C</span>
174181
</div>,
175182
);
176183

177-
awaitserverAct(()=>C.resolve());
178-
assertLog(['C']);
184+
awaitserverAct(()=>A.resolve());
185+
assertLog(['A']);
179186

180187
expect(getVisibleChildren(container)).toEqual(
181188
<div>
182189
<span>A</span>
183190
<span>Loading B</span>
184-
<span>C</span>
191+
<span>Loading C</span>
185192
</div>,
186193
);
187194

‎packages/react-reconciler/src/ReactChildFiber.js‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2104,7 +2104,8 @@ export function validateSuspenseListChildren(
21042104
){
21052105
if(__DEV__){
21062106
if(
2107-
(revealOrder==='forwards'||
2107+
(revealOrder==null||
2108+
revealOrder==='forwards'||
21082109
revealOrder==='backwards'||
21092110
revealOrder==='unstable_legacy-backwards')&&
21102111
children!==undefined&&

‎packages/react-reconciler/src/ReactFiberBeginWork.js‎

Lines changed: 34 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -3245,20 +3245,15 @@ function validateRevealOrder(revealOrder: SuspenseListRevealOrder) {
32453245
if(__DEV__){
32463246
constcacheKey=revealOrder==null ? 'null' : revealOrder;
32473247
if(
3248+
revealOrder!=null&&
32483249
revealOrder!=='forwards'&&
32493250
revealOrder!=='unstable_legacy-backwards'&&
32503251
revealOrder!=='together'&&
32513252
revealOrder!=='independent'&&
32523253
!didWarnAboutRevealOrder[cacheKey]
32533254
){
32543255
didWarnAboutRevealOrder[cacheKey]=true;
3255-
if(revealOrder==null){
3256-
console.error(
3257-
'The default for the <SuspenseList revealOrder="..."> prop is changing. '+
3258-
'To be future compatible you must explictly specify either '+
3259-
'"independent" (the current default), "together", "forwards" or "legacy_unstable-backwards".',
3260-
);
3261-
}elseif(revealOrder==='backwards'){
3256+
if(revealOrder==='backwards'){
32623257
console.error(
32633258
'The rendering order of <SuspenseList revealOrder="backwards"> is changing. '+
32643259
'To be future compatible you must specify revealOrder="legacy_unstable-backwards" instead.',
@@ -3314,18 +3309,7 @@ function validateTailOptions(
33143309
constcacheKey=tailMode==null ? 'null' : tailMode;
33153310
if(!didWarnAboutTailOptions[cacheKey]){
33163311
if(tailMode==null){
3317-
if(
3318-
revealOrder==='forwards'||
3319-
revealOrder==='backwards'||
3320-
revealOrder==='unstable_legacy-backwards'
3321-
){
3322-
didWarnAboutTailOptions[cacheKey]=true;
3323-
console.error(
3324-
'The default for the <SuspenseList tail="..."> prop is changing. '+
3325-
'To be future compatible you must explictly specify either '+
3326-
'"visible" (the current default), "collapsed" or "hidden".',
3327-
);
3328-
}
3312+
// The default tail is now "hidden".
33293313
}elseif(
33303314
tailMode!=='visible'&&
33313315
tailMode!=='collapsed'&&
@@ -3338,14 +3322,15 @@ function validateTailOptions(
33383322
tailMode,
33393323
);
33403324
}elseif(
3325+
revealOrder!=null&&
33413326
revealOrder!=='forwards'&&
33423327
revealOrder!=='backwards'&&
33433328
revealOrder!=='unstable_legacy-backwards'
33443329
){
33453330
didWarnAboutTailOptions[cacheKey]=true;
33463331
console.error(
33473332
'<SuspenseList tail="%s" /> is only valid if revealOrder is '+
3348-
'"forwards" or "backwards". '+
3333+
'"forwards" (default) or "backwards". '+
33493334
'Did you mean to specify revealOrder="forwards"?',
33503335
tailMode,
33513336
);
@@ -3449,30 +3434,6 @@ function updateSuspenseListComponent(
34493434
workInProgress.memoizedState=null;
34503435
}else{
34513436
switch(revealOrder){
3452-
case'forwards': {
3453-
constlastContentRow=findLastContentRow(workInProgress.child);
3454-
lettail;
3455-
if(lastContentRow===null){
3456-
// The whole list is part of the tail.
3457-
// TODO: We could fast path by just rendering the tail now.
3458-
tail=workInProgress.child;
3459-
workInProgress.child=null;
3460-
}else{
3461-
// Disconnect the tail rows after the content row.
3462-
// We're going to render them separately later.
3463-
tail=lastContentRow.sibling;
3464-
lastContentRow.sibling=null;
3465-
}
3466-
initSuspenseListRenderState(
3467-
workInProgress,
3468-
false,// isBackwards
3469-
tail,
3470-
lastContentRow,
3471-
tailMode,
3472-
treeForkCount,
3473-
);
3474-
break;
3475-
}
34763437
case'backwards':
34773438
case'unstable_legacy-backwards': {
34783439
// We're going to find the first row that has existing content.
@@ -3517,10 +3478,37 @@ function updateSuspenseListComponent(
35173478
);
35183479
break;
35193480
}
3520-
default: {
3521-
// The default reveal order is the same as not having
3481+
case'independent': {
3482+
// The "independent" reveal order is the same as not having
35223483
// a boundary.
35233484
workInProgress.memoizedState=null;
3485+
break;
3486+
}
3487+
// The default is now forwards.
3488+
case'forwards':
3489+
default: {
3490+
constlastContentRow=findLastContentRow(workInProgress.child);
3491+
lettail;
3492+
if(lastContentRow===null){
3493+
// The whole list is part of the tail.
3494+
// TODO: We could fast path by just rendering the tail now.
3495+
tail=workInProgress.child;
3496+
workInProgress.child=null;
3497+
}else{
3498+
// Disconnect the tail rows after the content row.
3499+
// We're going to render them separately later.
3500+
tail=lastContentRow.sibling;
3501+
lastContentRow.sibling=null;
3502+
}
3503+
initSuspenseListRenderState(
3504+
workInProgress,
3505+
false,// isBackwards
3506+
tail,
3507+
lastContentRow,
3508+
tailMode,
3509+
treeForkCount,
3510+
);
3511+
break;
35243512
}
35253513
}
35263514
}

‎packages/react-reconciler/src/ReactFiberCompleteWork.js‎

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -698,7 +698,11 @@ function cutOffTailIfNeeded(
698698
return;
699699
}
700700
switch(renderState.tailMode){
701-
case'hidden': {
701+
case'visible': {
702+
// Everything should remain as it was.
703+
break;
704+
}
705+
case'collapsed': {
702706
// Any insertions at the end of the tail list after this point
703707
// should be invisible. If there are already mounted boundaries
704708
// anything before them are not considered for collapsing.
@@ -716,15 +720,23 @@ function cutOffTailIfNeeded(
716720
// last rendered item.
717721
if(lastTailNode===null){
718722
// All remaining items in the tail are insertions.
719-
renderState.tail=null;
723+
if(!hasRenderedATailFallback&&renderState.tail!==null){
724+
// We suspended during the head. We want to show at least one
725+
// row at the tail. So we'll keep on and cut off the rest.
726+
renderState.tail.sibling=null;
727+
}else{
728+
renderState.tail=null;
729+
}
720730
}else{
721731
// Detach the insertion after the last node that was already
722732
// inserted.
723733
lastTailNode.sibling=null;
724734
}
725735
break;
726736
}
727-
case'collapsed': {
737+
// Hidden is now the default.
738+
case'hidden':
739+
default: {
728740
// Any insertions at the end of the tail list after this point
729741
// should be invisible. If there are already mounted boundaries
730742
// anything before them are not considered for collapsing.
@@ -742,13 +754,7 @@ function cutOffTailIfNeeded(
742754
// last rendered item.
743755
if(lastTailNode===null){
744756
// All remaining items in the tail are insertions.
745-
if(!hasRenderedATailFallback&&renderState.tail!==null){
746-
// We suspended during the head. We want to show at least one
747-
// row at the tail. So we'll keep on and cut off the rest.
748-
renderState.tail.sibling=null;
749-
}else{
750-
renderState.tail=null;
751-
}
757+
renderState.tail=null;
752758
}else{
753759
// Detach the insertion after the last node that was already
754760
// inserted.
@@ -1795,7 +1801,8 @@ function completeWork(
17951801
// This might have been modified.
17961802
if(
17971803
renderState.tail===null&&
1798-
renderState.tailMode==='hidden'&&
1804+
renderState.tailMode!=='collapsed'&&
1805+
renderState.tailMode!=='visible'&&
17991806
!renderedTail.alternate&&
18001807
!getIsHydrating()// We don't cut it if we're hydrating.
18011808
){

‎packages/react-reconciler/src/ReactFiberSuspenseComponent.js‎

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,7 @@ export function findFirstSuspended(row: Fiber): null | Fiber {
7979
node.tag===SuspenseListComponent&&
8080
// Independent revealOrder can't be trusted because it doesn't
8181
// keep track of whether it suspended or not.
82-
(node.memoizedProps.revealOrder==='forwards'||
83-
node.memoizedProps.revealOrder==='backwards'||
84-
node.memoizedProps.revealOrder==='unstable_legacy-backwards'||
85-
node.memoizedProps.revealOrder==='together')
82+
node.memoizedProps.revealOrder!=='independent'
8683
){
8784
constdidSuspend=(node.flags&DidCapture)!==NoFlags;
8885
if(didSuspend){

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Switch the default revealOrder to "forwards" and tail "hidden" on Sus… · react/react@26cf280 · GitHub
Skip to content

Commit 26cf280

Browse files
authored
Switch the default revealOrder to "forwards" and tail "hidden" on SuspenseList (#35018)
We have warned about this for a while now so we can make the switch. Often when you reach for SuspenseList, you mean forwards. It doesn't make sense to have the default to just be a noop. While "together" is another useful mode that's more like a Group so isn't so associated with the default as List. So we're switching it. However, tail=hidden isn't as obvious of a default it does allow for a convenient pattern for streaming in list of items by default. This doesn't yet switch the rendering order of "backwards". That's coming in a follow up.
1 parent c9ddee7 commit 26cf280

7 files changed

Lines changed: 142 additions & 125 deletions

File tree

‎packages/react-dom/src/__tests__/ReactDOMFizzSuspenseList-test.js‎

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ describe('ReactDOMFizzSuspenseList', () => {
134134
}
135135

136136
// @gate enableSuspenseList
137-
it('shows content independently by default',async()=>{
137+
it('shows content forwards by default',async()=>{
138138
constA=createAsyncText('A');
139139
constB=createAsyncText('B');
140140
constC=createAsyncText('C');
@@ -157,31 +157,38 @@ describe('ReactDOMFizzSuspenseList', () => {
157157
);
158158
}
159159

160-
awaitA.resolve();
160+
awaitC.resolve();
161161

162162
awaitserverAct(async()=>{
163163
const{pipe}=ReactDOMFizzServer.renderToPipeableStream(<Foo/>);
164164
pipe(writable);
165165
});
166166

167-
assertLog(['A','Suspend! [B]','Suspend! [C]','Loading B','Loading C']);
167+
assertLog([
168+
'Suspend! [A]',
169+
'Suspend! [B]',// TODO: Defer rendering the content after fallback if previous suspended,
170+
'C',
171+
'Loading A',
172+
'Loading B',
173+
'Loading C',
174+
]);
168175

169176
expect(getVisibleChildren(container)).toEqual(
170177
<div>
171-
<span>A</span>
178+
<span>Loading A</span>
172179
<span>Loading B</span>
173180
<span>Loading C</span>
174181
</div>,
175182
);
176183

177-
awaitserverAct(()=>C.resolve());
178-
assertLog(['C']);
184+
awaitserverAct(()=>A.resolve());
185+
assertLog(['A']);
179186

180187
expect(getVisibleChildren(container)).toEqual(
181188
<div>
182189
<span>A</span>
183190
<span>Loading B</span>
184-
<span>C</span>
191+
<span>Loading C</span>
185192
</div>,
186193
);
187194

‎packages/react-reconciler/src/ReactChildFiber.js‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2104,7 +2104,8 @@ export function validateSuspenseListChildren(
21042104
){
21052105
if(__DEV__){
21062106
if(
2107-
(revealOrder==='forwards'||
2107+
(revealOrder==null||
2108+
revealOrder==='forwards'||
21082109
revealOrder==='backwards'||
21092110
revealOrder==='unstable_legacy-backwards')&&
21102111
children!==undefined&&

‎packages/react-reconciler/src/ReactFiberBeginWork.js‎

Lines changed: 34 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -3245,20 +3245,15 @@ function validateRevealOrder(revealOrder: SuspenseListRevealOrder) {
32453245
if(__DEV__){
32463246
constcacheKey=revealOrder==null ? 'null' : revealOrder;
32473247
if(
3248+
revealOrder!=null&&
32483249
revealOrder!=='forwards'&&
32493250
revealOrder!=='unstable_legacy-backwards'&&
32503251
revealOrder!=='together'&&
32513252
revealOrder!=='independent'&&
32523253
!didWarnAboutRevealOrder[cacheKey]
32533254
){
32543255
didWarnAboutRevealOrder[cacheKey]=true;
3255-
if(revealOrder==null){
3256-
console.error(
3257-
'The default for the <SuspenseList revealOrder="..."> prop is changing. '+
3258-
'To be future compatible you must explictly specify either '+
3259-
'"independent" (the current default), "together", "forwards" or "legacy_unstable-backwards".',
3260-
);
3261-
}elseif(revealOrder==='backwards'){
3256+
if(revealOrder==='backwards'){
32623257
console.error(
32633258
'The rendering order of <SuspenseList revealOrder="backwards"> is changing. '+
32643259
'To be future compatible you must specify revealOrder="legacy_unstable-backwards" instead.',
@@ -3314,18 +3309,7 @@ function validateTailOptions(
33143309
constcacheKey=tailMode==null ? 'null' : tailMode;
33153310
if(!didWarnAboutTailOptions[cacheKey]){
33163311
if(tailMode==null){
3317-
if(
3318-
revealOrder==='forwards'||
3319-
revealOrder==='backwards'||
3320-
revealOrder==='unstable_legacy-backwards'
3321-
){
3322-
didWarnAboutTailOptions[cacheKey]=true;
3323-
console.error(
3324-
'The default for the <SuspenseList tail="..."> prop is changing. '+
3325-
'To be future compatible you must explictly specify either '+
3326-
'"visible" (the current default), "collapsed" or "hidden".',
3327-
);
3328-
}
3312+
// The default tail is now "hidden".
33293313
}elseif(
33303314
tailMode!=='visible'&&
33313315
tailMode!=='collapsed'&&
@@ -3338,14 +3322,15 @@ function validateTailOptions(
33383322
tailMode,
33393323
);
33403324
}elseif(
3325+
revealOrder!=null&&
33413326
revealOrder!=='forwards'&&
33423327
revealOrder!=='backwards'&&
33433328
revealOrder!=='unstable_legacy-backwards'
33443329
){
33453330
didWarnAboutTailOptions[cacheKey]=true;
33463331
console.error(
33473332
'<SuspenseList tail="%s" /> is only valid if revealOrder is '+
3348-
'"forwards" or "backwards". '+
3333+
'"forwards" (default) or "backwards". '+
33493334
'Did you mean to specify revealOrder="forwards"?',
33503335
tailMode,
33513336
);
@@ -3449,30 +3434,6 @@ function updateSuspenseListComponent(
34493434
workInProgress.memoizedState=null;
34503435
}else{
34513436
switch(revealOrder){
3452-
case'forwards': {
3453-
constlastContentRow=findLastContentRow(workInProgress.child);
3454-
lettail;
3455-
if(lastContentRow===null){
3456-
// The whole list is part of the tail.
3457-
// TODO: We could fast path by just rendering the tail now.
3458-
tail=workInProgress.child;
3459-
workInProgress.child=null;
3460-
}else{
3461-
// Disconnect the tail rows after the content row.
3462-
// We're going to render them separately later.
3463-
tail=lastContentRow.sibling;
3464-
lastContentRow.sibling=null;
3465-
}
3466-
initSuspenseListRenderState(
3467-
workInProgress,
3468-
false,// isBackwards
3469-
tail,
3470-
lastContentRow,
3471-
tailMode,
3472-
treeForkCount,
3473-
);
3474-
break;
3475-
}
34763437
case'backwards':
34773438
case'unstable_legacy-backwards': {
34783439
// We're going to find the first row that has existing content.
@@ -3517,10 +3478,37 @@ function updateSuspenseListComponent(
35173478
);
35183479
break;
35193480
}
3520-
default: {
3521-
// The default reveal order is the same as not having
3481+
case'independent': {
3482+
// The "independent" reveal order is the same as not having
35223483
// a boundary.
35233484
workInProgress.memoizedState=null;
3485+
break;
3486+
}
3487+
// The default is now forwards.
3488+
case'forwards':
3489+
default: {
3490+
constlastContentRow=findLastContentRow(workInProgress.child);
3491+
lettail;
3492+
if(lastContentRow===null){
3493+
// The whole list is part of the tail.
3494+
// TODO: We could fast path by just rendering the tail now.
3495+
tail=workInProgress.child;
3496+
workInProgress.child=null;
3497+
}else{
3498+
// Disconnect the tail rows after the content row.
3499+
// We're going to render them separately later.
3500+
tail=lastContentRow.sibling;
3501+
lastContentRow.sibling=null;
3502+
}
3503+
initSuspenseListRenderState(
3504+
workInProgress,
3505+
false,// isBackwards
3506+
tail,
3507+
lastContentRow,
3508+
tailMode,
3509+
treeForkCount,
3510+
);
3511+
break;
35243512
}
35253513
}
35263514
}

‎packages/react-reconciler/src/ReactFiberCompleteWork.js‎

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -698,7 +698,11 @@ function cutOffTailIfNeeded(
698698
return;
699699
}
700700
switch(renderState.tailMode){
701-
case'hidden': {
701+
case'visible': {
702+
// Everything should remain as it was.
703+
break;
704+
}
705+
case'collapsed': {
702706
// Any insertions at the end of the tail list after this point
703707
// should be invisible. If there are already mounted boundaries
704708
// anything before them are not considered for collapsing.
@@ -716,15 +720,23 @@ function cutOffTailIfNeeded(
716720
// last rendered item.
717721
if(lastTailNode===null){
718722
// All remaining items in the tail are insertions.
719-
renderState.tail=null;
723+
if(!hasRenderedATailFallback&&renderState.tail!==null){
724+
// We suspended during the head. We want to show at least one
725+
// row at the tail. So we'll keep on and cut off the rest.
726+
renderState.tail.sibling=null;
727+
}else{
728+
renderState.tail=null;
729+
}
720730
}else{
721731
// Detach the insertion after the last node that was already
722732
// inserted.
723733
lastTailNode.sibling=null;
724734
}
725735
break;
726736
}
727-
case'collapsed': {
737+
// Hidden is now the default.
738+
case'hidden':
739+
default: {
728740
// Any insertions at the end of the tail list after this point
729741
// should be invisible. If there are already mounted boundaries
730742
// anything before them are not considered for collapsing.
@@ -742,13 +754,7 @@ function cutOffTailIfNeeded(
742754
// last rendered item.
743755
if(lastTailNode===null){
744756
// All remaining items in the tail are insertions.
745-
if(!hasRenderedATailFallback&&renderState.tail!==null){
746-
// We suspended during the head. We want to show at least one
747-
// row at the tail. So we'll keep on and cut off the rest.
748-
renderState.tail.sibling=null;
749-
}else{
750-
renderState.tail=null;
751-
}
757+
renderState.tail=null;
752758
}else{
753759
// Detach the insertion after the last node that was already
754760
// inserted.
@@ -1795,7 +1801,8 @@ function completeWork(
17951801
// This might have been modified.
17961802
if(
17971803
renderState.tail===null&&
1798-
renderState.tailMode==='hidden'&&
1804+
renderState.tailMode!=='collapsed'&&
1805+
renderState.tailMode!=='visible'&&
17991806
!renderedTail.alternate&&
18001807
!getIsHydrating()// We don't cut it if we're hydrating.
18011808
){

‎packages/react-reconciler/src/ReactFiberSuspenseComponent.js‎

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,7 @@ export function findFirstSuspended(row: Fiber): null | Fiber {
7979
node.tag===SuspenseListComponent&&
8080
// Independent revealOrder can't be trusted because it doesn't
8181
// keep track of whether it suspended or not.
82-
(node.memoizedProps.revealOrder==='forwards'||
83-
node.memoizedProps.revealOrder==='backwards'||
84-
node.memoizedProps.revealOrder==='unstable_legacy-backwards'||
85-
node.memoizedProps.revealOrder==='together')
82+
node.memoizedProps.revealOrder!=='independent'
8683
){
8784
constdidSuspend=(node.flags&DidCapture)!==NoFlags;
8885
if(didSuspend){

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Switch the default revealOrder to "forwards" and tail "hidden" on Sus… · react/react@26cf280 · GitHub
Skip to content

Commit 26cf280

Browse files
authored
Switch the default revealOrder to "forwards" and tail "hidden" on SuspenseList (#35018)
We have warned about this for a while now so we can make the switch. Often when you reach for SuspenseList, you mean forwards. It doesn't make sense to have the default to just be a noop. While "together" is another useful mode that's more like a Group so isn't so associated with the default as List. So we're switching it. However, tail=hidden isn't as obvious of a default it does allow for a convenient pattern for streaming in list of items by default. This doesn't yet switch the rendering order of "backwards". That's coming in a follow up.
1 parent c9ddee7 commit 26cf280

7 files changed

Lines changed: 142 additions & 125 deletions

File tree

‎packages/react-dom/src/__tests__/ReactDOMFizzSuspenseList-test.js‎

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ describe('ReactDOMFizzSuspenseList', () => {
134134
}
135135

136136
// @gate enableSuspenseList
137-
it('shows content independently by default',async()=>{
137+
it('shows content forwards by default',async()=>{
138138
constA=createAsyncText('A');
139139
constB=createAsyncText('B');
140140
constC=createAsyncText('C');
@@ -157,31 +157,38 @@ describe('ReactDOMFizzSuspenseList', () => {
157157
);
158158
}
159159

160-
awaitA.resolve();
160+
awaitC.resolve();
161161

162162
awaitserverAct(async()=>{
163163
const{pipe}=ReactDOMFizzServer.renderToPipeableStream(<Foo/>);
164164
pipe(writable);
165165
});
166166

167-
assertLog(['A','Suspend! [B]','Suspend! [C]','Loading B','Loading C']);
167+
assertLog([
168+
'Suspend! [A]',
169+
'Suspend! [B]',// TODO: Defer rendering the content after fallback if previous suspended,
170+
'C',
171+
'Loading A',
172+
'Loading B',
173+
'Loading C',
174+
]);
168175

169176
expect(getVisibleChildren(container)).toEqual(
170177
<div>
171-
<span>A</span>
178+
<span>Loading A</span>
172179
<span>Loading B</span>
173180
<span>Loading C</span>
174181
</div>,
175182
);
176183

177-
awaitserverAct(()=>C.resolve());
178-
assertLog(['C']);
184+
awaitserverAct(()=>A.resolve());
185+
assertLog(['A']);
179186

180187
expect(getVisibleChildren(container)).toEqual(
181188
<div>
182189
<span>A</span>
183190
<span>Loading B</span>
184-
<span>C</span>
191+
<span>Loading C</span>
185192
</div>,
186193
);
187194

‎packages/react-reconciler/src/ReactChildFiber.js‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2104,7 +2104,8 @@ export function validateSuspenseListChildren(
21042104
){
21052105
if(__DEV__){
21062106
if(
2107-
(revealOrder==='forwards'||
2107+
(revealOrder==null||
2108+
revealOrder==='forwards'||
21082109
revealOrder==='backwards'||
21092110
revealOrder==='unstable_legacy-backwards')&&
21102111
children!==undefined&&

‎packages/react-reconciler/src/ReactFiberBeginWork.js‎

Lines changed: 34 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -3245,20 +3245,15 @@ function validateRevealOrder(revealOrder: SuspenseListRevealOrder) {
32453245
if(__DEV__){
32463246
constcacheKey=revealOrder==null ? 'null' : revealOrder;
32473247
if(
3248+
revealOrder!=null&&
32483249
revealOrder!=='forwards'&&
32493250
revealOrder!=='unstable_legacy-backwards'&&
32503251
revealOrder!=='together'&&
32513252
revealOrder!=='independent'&&
32523253
!didWarnAboutRevealOrder[cacheKey]
32533254
){
32543255
didWarnAboutRevealOrder[cacheKey]=true;
3255-
if(revealOrder==null){
3256-
console.error(
3257-
'The default for the <SuspenseList revealOrder="..."> prop is changing. '+
3258-
'To be future compatible you must explictly specify either '+
3259-
'"independent" (the current default), "together", "forwards" or "legacy_unstable-backwards".',
3260-
);
3261-
}elseif(revealOrder==='backwards'){
3256+
if(revealOrder==='backwards'){
32623257
console.error(
32633258
'The rendering order of <SuspenseList revealOrder="backwards"> is changing. '+
32643259
'To be future compatible you must specify revealOrder="legacy_unstable-backwards" instead.',
@@ -3314,18 +3309,7 @@ function validateTailOptions(
33143309
constcacheKey=tailMode==null ? 'null' : tailMode;
33153310
if(!didWarnAboutTailOptions[cacheKey]){
33163311
if(tailMode==null){
3317-
if(
3318-
revealOrder==='forwards'||
3319-
revealOrder==='backwards'||
3320-
revealOrder==='unstable_legacy-backwards'
3321-
){
3322-
didWarnAboutTailOptions[cacheKey]=true;
3323-
console.error(
3324-
'The default for the <SuspenseList tail="..."> prop is changing. '+
3325-
'To be future compatible you must explictly specify either '+
3326-
'"visible" (the current default), "collapsed" or "hidden".',
3327-
);
3328-
}
3312+
// The default tail is now "hidden".
33293313
}elseif(
33303314
tailMode!=='visible'&&
33313315
tailMode!=='collapsed'&&
@@ -3338,14 +3322,15 @@ function validateTailOptions(
33383322
tailMode,
33393323
);
33403324
}elseif(
3325+
revealOrder!=null&&
33413326
revealOrder!=='forwards'&&
33423327
revealOrder!=='backwards'&&
33433328
revealOrder!=='unstable_legacy-backwards'
33443329
){
33453330
didWarnAboutTailOptions[cacheKey]=true;
33463331
console.error(
33473332
'<SuspenseList tail="%s" /> is only valid if revealOrder is '+
3348-
'"forwards" or "backwards". '+
3333+
'"forwards" (default) or "backwards". '+
33493334
'Did you mean to specify revealOrder="forwards"?',
33503335
tailMode,
33513336
);
@@ -3449,30 +3434,6 @@ function updateSuspenseListComponent(
34493434
workInProgress.memoizedState=null;
34503435
}else{
34513436
switch(revealOrder){
3452-
case'forwards': {
3453-
constlastContentRow=findLastContentRow(workInProgress.child);
3454-
lettail;
3455-
if(lastContentRow===null){
3456-
// The whole list is part of the tail.
3457-
// TODO: We could fast path by just rendering the tail now.
3458-
tail=workInProgress.child;
3459-
workInProgress.child=null;
3460-
}else{
3461-
// Disconnect the tail rows after the content row.
3462-
// We're going to render them separately later.
3463-
tail=lastContentRow.sibling;
3464-
lastContentRow.sibling=null;
3465-
}
3466-
initSuspenseListRenderState(
3467-
workInProgress,
3468-
false,// isBackwards
3469-
tail,
3470-
lastContentRow,
3471-
tailMode,
3472-
treeForkCount,
3473-
);
3474-
break;
3475-
}
34763437
case'backwards':
34773438
case'unstable_legacy-backwards': {
34783439
// We're going to find the first row that has existing content.
@@ -3517,10 +3478,37 @@ function updateSuspenseListComponent(
35173478
);
35183479
break;
35193480
}
3520-
default: {
3521-
// The default reveal order is the same as not having
3481+
case'independent': {
3482+
// The "independent" reveal order is the same as not having
35223483
// a boundary.
35233484
workInProgress.memoizedState=null;
3485+
break;
3486+
}
3487+
// The default is now forwards.
3488+
case'forwards':
3489+
default: {
3490+
constlastContentRow=findLastContentRow(workInProgress.child);
3491+
lettail;
3492+
if(lastContentRow===null){
3493+
// The whole list is part of the tail.
3494+
// TODO: We could fast path by just rendering the tail now.
3495+
tail=workInProgress.child;
3496+
workInProgress.child=null;
3497+
}else{
3498+
// Disconnect the tail rows after the content row.
3499+
// We're going to render them separately later.
3500+
tail=lastContentRow.sibling;
3501+
lastContentRow.sibling=null;
3502+
}
3503+
initSuspenseListRenderState(
3504+
workInProgress,
3505+
false,// isBackwards
3506+
tail,
3507+
lastContentRow,
3508+
tailMode,
3509+
treeForkCount,
3510+
);
3511+
break;
35243512
}
35253513
}
35263514
}

‎packages/react-reconciler/src/ReactFiberCompleteWork.js‎

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -698,7 +698,11 @@ function cutOffTailIfNeeded(
698698
return;
699699
}
700700
switch(renderState.tailMode){
701-
case'hidden': {
701+
case'visible': {
702+
// Everything should remain as it was.
703+
break;
704+
}
705+
case'collapsed': {
702706
// Any insertions at the end of the tail list after this point
703707
// should be invisible. If there are already mounted boundaries
704708
// anything before them are not considered for collapsing.
@@ -716,15 +720,23 @@ function cutOffTailIfNeeded(
716720
// last rendered item.
717721
if(lastTailNode===null){
718722
// All remaining items in the tail are insertions.
719-
renderState.tail=null;
723+
if(!hasRenderedATailFallback&&renderState.tail!==null){
724+
// We suspended during the head. We want to show at least one
725+
// row at the tail. So we'll keep on and cut off the rest.
726+
renderState.tail.sibling=null;
727+
}else{
728+
renderState.tail=null;
729+
}
720730
}else{
721731
// Detach the insertion after the last node that was already
722732
// inserted.
723733
lastTailNode.sibling=null;
724734
}
725735
break;
726736
}
727-
case'collapsed': {
737+
// Hidden is now the default.
738+
case'hidden':
739+
default: {
728740
// Any insertions at the end of the tail list after this point
729741
// should be invisible. If there are already mounted boundaries
730742
// anything before them are not considered for collapsing.
@@ -742,13 +754,7 @@ function cutOffTailIfNeeded(
742754
// last rendered item.
743755
if(lastTailNode===null){
744756
// All remaining items in the tail are insertions.
745-
if(!hasRenderedATailFallback&&renderState.tail!==null){
746-
// We suspended during the head. We want to show at least one
747-
// row at the tail. So we'll keep on and cut off the rest.
748-
renderState.tail.sibling=null;
749-
}else{
750-
renderState.tail=null;
751-
}
757+
renderState.tail=null;
752758
}else{
753759
// Detach the insertion after the last node that was already
754760
// inserted.
@@ -1795,7 +1801,8 @@ function completeWork(
17951801
// This might have been modified.
17961802
if(
17971803
renderState.tail===null&&
1798-
renderState.tailMode==='hidden'&&
1804+
renderState.tailMode!=='collapsed'&&
1805+
renderState.tailMode!=='visible'&&
17991806
!renderedTail.alternate&&
18001807
!getIsHydrating()// We don't cut it if we're hydrating.
18011808
){

‎packages/react-reconciler/src/ReactFiberSuspenseComponent.js‎

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,7 @@ export function findFirstSuspended(row: Fiber): null | Fiber {
7979
node.tag===SuspenseListComponent&&
8080
// Independent revealOrder can't be trusted because it doesn't
8181
// keep track of whether it suspended or not.
82-
(node.memoizedProps.revealOrder==='forwards'||
83-
node.memoizedProps.revealOrder==='backwards'||
84-
node.memoizedProps.revealOrder==='unstable_legacy-backwards'||
85-
node.memoizedProps.revealOrder==='together')
82+
node.memoizedProps.revealOrder!=='independent'
8683
){
8784
constdidSuspend=(node.flags&DidCapture)!==NoFlags;
8885
if(didSuspend){

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Switch the default revealOrder to "forwards" and tail "hidden" on Sus… · react/react@26cf280 · GitHub
Skip to content

Commit 26cf280

Browse files
authored
Switch the default revealOrder to "forwards" and tail "hidden" on SuspenseList (#35018)
We have warned about this for a while now so we can make the switch. Often when you reach for SuspenseList, you mean forwards. It doesn't make sense to have the default to just be a noop. While "together" is another useful mode that's more like a Group so isn't so associated with the default as List. So we're switching it. However, tail=hidden isn't as obvious of a default it does allow for a convenient pattern for streaming in list of items by default. This doesn't yet switch the rendering order of "backwards". That's coming in a follow up.
1 parent c9ddee7 commit 26cf280

7 files changed

Lines changed: 142 additions & 125 deletions

File tree

‎packages/react-dom/src/__tests__/ReactDOMFizzSuspenseList-test.js‎

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ describe('ReactDOMFizzSuspenseList', () => {
134134
}
135135

136136
// @gate enableSuspenseList
137-
it('shows content independently by default',async()=>{
137+
it('shows content forwards by default',async()=>{
138138
constA=createAsyncText('A');
139139
constB=createAsyncText('B');
140140
constC=createAsyncText('C');
@@ -157,31 +157,38 @@ describe('ReactDOMFizzSuspenseList', () => {
157157
);
158158
}
159159

160-
awaitA.resolve();
160+
awaitC.resolve();
161161

162162
awaitserverAct(async()=>{
163163
const{pipe}=ReactDOMFizzServer.renderToPipeableStream(<Foo/>);
164164
pipe(writable);
165165
});
166166

167-
assertLog(['A','Suspend! [B]','Suspend! [C]','Loading B','Loading C']);
167+
assertLog([
168+
'Suspend! [A]',
169+
'Suspend! [B]',// TODO: Defer rendering the content after fallback if previous suspended,
170+
'C',
171+
'Loading A',
172+
'Loading B',
173+
'Loading C',
174+
]);
168175

169176
expect(getVisibleChildren(container)).toEqual(
170177
<div>
171-
<span>A</span>
178+
<span>Loading A</span>
172179
<span>Loading B</span>
173180
<span>Loading C</span>
174181
</div>,
175182
);
176183

177-
awaitserverAct(()=>C.resolve());
178-
assertLog(['C']);
184+
awaitserverAct(()=>A.resolve());
185+
assertLog(['A']);
179186

180187
expect(getVisibleChildren(container)).toEqual(
181188
<div>
182189
<span>A</span>
183190
<span>Loading B</span>
184-
<span>C</span>
191+
<span>Loading C</span>
185192
</div>,
186193
);
187194

‎packages/react-reconciler/src/ReactChildFiber.js‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2104,7 +2104,8 @@ export function validateSuspenseListChildren(
21042104
){
21052105
if(__DEV__){
21062106
if(
2107-
(revealOrder==='forwards'||
2107+
(revealOrder==null||
2108+
revealOrder==='forwards'||
21082109
revealOrder==='backwards'||
21092110
revealOrder==='unstable_legacy-backwards')&&
21102111
children!==undefined&&

‎packages/react-reconciler/src/ReactFiberBeginWork.js‎

Lines changed: 34 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -3245,20 +3245,15 @@ function validateRevealOrder(revealOrder: SuspenseListRevealOrder) {
32453245
if(__DEV__){
32463246
constcacheKey=revealOrder==null ? 'null' : revealOrder;
32473247
if(
3248+
revealOrder!=null&&
32483249
revealOrder!=='forwards'&&
32493250
revealOrder!=='unstable_legacy-backwards'&&
32503251
revealOrder!=='together'&&
32513252
revealOrder!=='independent'&&
32523253
!didWarnAboutRevealOrder[cacheKey]
32533254
){
32543255
didWarnAboutRevealOrder[cacheKey]=true;
3255-
if(revealOrder==null){
3256-
console.error(
3257-
'The default for the <SuspenseList revealOrder="..."> prop is changing. '+
3258-
'To be future compatible you must explictly specify either '+
3259-
'"independent" (the current default), "together", "forwards" or "legacy_unstable-backwards".',
3260-
);
3261-
}elseif(revealOrder==='backwards'){
3256+
if(revealOrder==='backwards'){
32623257
console.error(
32633258
'The rendering order of <SuspenseList revealOrder="backwards"> is changing. '+
32643259
'To be future compatible you must specify revealOrder="legacy_unstable-backwards" instead.',
@@ -3314,18 +3309,7 @@ function validateTailOptions(
33143309
constcacheKey=tailMode==null ? 'null' : tailMode;
33153310
if(!didWarnAboutTailOptions[cacheKey]){
33163311
if(tailMode==null){
3317-
if(
3318-
revealOrder==='forwards'||
3319-
revealOrder==='backwards'||
3320-
revealOrder==='unstable_legacy-backwards'
3321-
){
3322-
didWarnAboutTailOptions[cacheKey]=true;
3323-
console.error(
3324-
'The default for the <SuspenseList tail="..."> prop is changing. '+
3325-
'To be future compatible you must explictly specify either '+
3326-
'"visible" (the current default), "collapsed" or "hidden".',
3327-
);
3328-
}
3312+
// The default tail is now "hidden".
33293313
}elseif(
33303314
tailMode!=='visible'&&
33313315
tailMode!=='collapsed'&&
@@ -3338,14 +3322,15 @@ function validateTailOptions(
33383322
tailMode,
33393323
);
33403324
}elseif(
3325+
revealOrder!=null&&
33413326
revealOrder!=='forwards'&&
33423327
revealOrder!=='backwards'&&
33433328
revealOrder!=='unstable_legacy-backwards'
33443329
){
33453330
didWarnAboutTailOptions[cacheKey]=true;
33463331
console.error(
33473332
'<SuspenseList tail="%s" /> is only valid if revealOrder is '+
3348-
'"forwards" or "backwards". '+
3333+
'"forwards" (default) or "backwards". '+
33493334
'Did you mean to specify revealOrder="forwards"?',
33503335
tailMode,
33513336
);
@@ -3449,30 +3434,6 @@ function updateSuspenseListComponent(
34493434
workInProgress.memoizedState=null;
34503435
}else{
34513436
switch(revealOrder){
3452-
case'forwards': {
3453-
constlastContentRow=findLastContentRow(workInProgress.child);
3454-
lettail;
3455-
if(lastContentRow===null){
3456-
// The whole list is part of the tail.
3457-
// TODO: We could fast path by just rendering the tail now.
3458-
tail=workInProgress.child;
3459-
workInProgress.child=null;
3460-
}else{
3461-
// Disconnect the tail rows after the content row.
3462-
// We're going to render them separately later.
3463-
tail=lastContentRow.sibling;
3464-
lastContentRow.sibling=null;
3465-
}
3466-
initSuspenseListRenderState(
3467-
workInProgress,
3468-
false,// isBackwards
3469-
tail,
3470-
lastContentRow,
3471-
tailMode,
3472-
treeForkCount,
3473-
);
3474-
break;
3475-
}
34763437
case'backwards':
34773438
case'unstable_legacy-backwards': {
34783439
// We're going to find the first row that has existing content.
@@ -3517,10 +3478,37 @@ function updateSuspenseListComponent(
35173478
);
35183479
break;
35193480
}
3520-
default: {
3521-
// The default reveal order is the same as not having
3481+
case'independent': {
3482+
// The "independent" reveal order is the same as not having
35223483
// a boundary.
35233484
workInProgress.memoizedState=null;
3485+
break;
3486+
}
3487+
// The default is now forwards.
3488+
case'forwards':
3489+
default: {
3490+
constlastContentRow=findLastContentRow(workInProgress.child);
3491+
lettail;
3492+
if(lastContentRow===null){
3493+
// The whole list is part of the tail.
3494+
// TODO: We could fast path by just rendering the tail now.
3495+
tail=workInProgress.child;
3496+
workInProgress.child=null;
3497+
}else{
3498+
// Disconnect the tail rows after the content row.
3499+
// We're going to render them separately later.
3500+
tail=lastContentRow.sibling;
3501+
lastContentRow.sibling=null;
3502+
}
3503+
initSuspenseListRenderState(
3504+
workInProgress,
3505+
false,// isBackwards
3506+
tail,
3507+
lastContentRow,
3508+
tailMode,
3509+
treeForkCount,
3510+
);
3511+
break;
35243512
}
35253513
}
35263514
}

‎packages/react-reconciler/src/ReactFiberCompleteWork.js‎

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -698,7 +698,11 @@ function cutOffTailIfNeeded(
698698
return;
699699
}
700700
switch(renderState.tailMode){
701-
case'hidden': {
701+
case'visible': {
702+
// Everything should remain as it was.
703+
break;
704+
}
705+
case'collapsed': {
702706
// Any insertions at the end of the tail list after this point
703707
// should be invisible. If there are already mounted boundaries
704708
// anything before them are not considered for collapsing.
@@ -716,15 +720,23 @@ function cutOffTailIfNeeded(
716720
// last rendered item.
717721
if(lastTailNode===null){
718722
// All remaining items in the tail are insertions.
719-
renderState.tail=null;
723+
if(!hasRenderedATailFallback&&renderState.tail!==null){
724+
// We suspended during the head. We want to show at least one
725+
// row at the tail. So we'll keep on and cut off the rest.
726+
renderState.tail.sibling=null;
727+
}else{
728+
renderState.tail=null;
729+
}
720730
}else{
721731
// Detach the insertion after the last node that was already
722732
// inserted.
723733
lastTailNode.sibling=null;
724734
}
725735
break;
726736
}
727-
case'collapsed': {
737+
// Hidden is now the default.
738+
case'hidden':
739+
default: {
728740
// Any insertions at the end of the tail list after this point
729741
// should be invisible. If there are already mounted boundaries
730742
// anything before them are not considered for collapsing.
@@ -742,13 +754,7 @@ function cutOffTailIfNeeded(
742754
// last rendered item.
743755
if(lastTailNode===null){
744756
// All remaining items in the tail are insertions.
745-
if(!hasRenderedATailFallback&&renderState.tail!==null){
746-
// We suspended during the head. We want to show at least one
747-
// row at the tail. So we'll keep on and cut off the rest.
748-
renderState.tail.sibling=null;
749-
}else{
750-
renderState.tail=null;
751-
}
757+
renderState.tail=null;
752758
}else{
753759
// Detach the insertion after the last node that was already
754760
// inserted.
@@ -1795,7 +1801,8 @@ function completeWork(
17951801
// This might have been modified.
17961802
if(
17971803
renderState.tail===null&&
1798-
renderState.tailMode==='hidden'&&
1804+
renderState.tailMode!=='collapsed'&&
1805+
renderState.tailMode!=='visible'&&
17991806
!renderedTail.alternate&&
18001807
!getIsHydrating()// We don't cut it if we're hydrating.
18011808
){

‎packages/react-reconciler/src/ReactFiberSuspenseComponent.js‎

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,7 @@ export function findFirstSuspended(row: Fiber): null | Fiber {
7979
node.tag===SuspenseListComponent&&
8080
// Independent revealOrder can't be trusted because it doesn't
8181
// keep track of whether it suspended or not.
82-
(node.memoizedProps.revealOrder==='forwards'||
83-
node.memoizedProps.revealOrder==='backwards'||
84-
node.memoizedProps.revealOrder==='unstable_legacy-backwards'||
85-
node.memoizedProps.revealOrder==='together')
82+
node.memoizedProps.revealOrder!=='independent'
8683
){
8784
constdidSuspend=(node.flags&DidCapture)!==NoFlags;
8885
if(didSuspend){

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Switch the default revealOrder to "forwards" and tail "hidden" on Sus… · react/react@26cf280 · GitHub
Skip to content

Commit 26cf280

Browse files
authored
Switch the default revealOrder to "forwards" and tail "hidden" on SuspenseList (#35018)
We have warned about this for a while now so we can make the switch. Often when you reach for SuspenseList, you mean forwards. It doesn't make sense to have the default to just be a noop. While "together" is another useful mode that's more like a Group so isn't so associated with the default as List. So we're switching it. However, tail=hidden isn't as obvious of a default it does allow for a convenient pattern for streaming in list of items by default. This doesn't yet switch the rendering order of "backwards". That's coming in a follow up.
1 parent c9ddee7 commit 26cf280

7 files changed

Lines changed: 142 additions & 125 deletions

File tree

‎packages/react-dom/src/__tests__/ReactDOMFizzSuspenseList-test.js‎

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ describe('ReactDOMFizzSuspenseList', () => {
134134
}
135135

136136
// @gate enableSuspenseList
137-
it('shows content independently by default',async()=>{
137+
it('shows content forwards by default',async()=>{
138138
constA=createAsyncText('A');
139139
constB=createAsyncText('B');
140140
constC=createAsyncText('C');
@@ -157,31 +157,38 @@ describe('ReactDOMFizzSuspenseList', () => {
157157
);
158158
}
159159

160-
awaitA.resolve();
160+
awaitC.resolve();
161161

162162
awaitserverAct(async()=>{
163163
const{pipe}=ReactDOMFizzServer.renderToPipeableStream(<Foo/>);
164164
pipe(writable);
165165
});
166166

167-
assertLog(['A','Suspend! [B]','Suspend! [C]','Loading B','Loading C']);
167+
assertLog([
168+
'Suspend! [A]',
169+
'Suspend! [B]',// TODO: Defer rendering the content after fallback if previous suspended,
170+
'C',
171+
'Loading A',
172+
'Loading B',
173+
'Loading C',
174+
]);
168175

169176
expect(getVisibleChildren(container)).toEqual(
170177
<div>
171-
<span>A</span>
178+
<span>Loading A</span>
172179
<span>Loading B</span>
173180
<span>Loading C</span>
174181
</div>,
175182
);
176183

177-
awaitserverAct(()=>C.resolve());
178-
assertLog(['C']);
184+
awaitserverAct(()=>A.resolve());
185+
assertLog(['A']);
179186

180187
expect(getVisibleChildren(container)).toEqual(
181188
<div>
182189
<span>A</span>
183190
<span>Loading B</span>
184-
<span>C</span>
191+
<span>Loading C</span>
185192
</div>,
186193
);
187194

‎packages/react-reconciler/src/ReactChildFiber.js‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2104,7 +2104,8 @@ export function validateSuspenseListChildren(
21042104
){
21052105
if(__DEV__){
21062106
if(
2107-
(revealOrder==='forwards'||
2107+
(revealOrder==null||
2108+
revealOrder==='forwards'||
21082109
revealOrder==='backwards'||
21092110
revealOrder==='unstable_legacy-backwards')&&
21102111
children!==undefined&&

‎packages/react-reconciler/src/ReactFiberBeginWork.js‎

Lines changed: 34 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -3245,20 +3245,15 @@ function validateRevealOrder(revealOrder: SuspenseListRevealOrder) {
32453245
if(__DEV__){
32463246
constcacheKey=revealOrder==null ? 'null' : revealOrder;
32473247
if(
3248+
revealOrder!=null&&
32483249
revealOrder!=='forwards'&&
32493250
revealOrder!=='unstable_legacy-backwards'&&
32503251
revealOrder!=='together'&&
32513252
revealOrder!=='independent'&&
32523253
!didWarnAboutRevealOrder[cacheKey]
32533254
){
32543255
didWarnAboutRevealOrder[cacheKey]=true;
3255-
if(revealOrder==null){
3256-
console.error(
3257-
'The default for the <SuspenseList revealOrder="..."> prop is changing. '+
3258-
'To be future compatible you must explictly specify either '+
3259-
'"independent" (the current default), "together", "forwards" or "legacy_unstable-backwards".',
3260-
);
3261-
}elseif(revealOrder==='backwards'){
3256+
if(revealOrder==='backwards'){
32623257
console.error(
32633258
'The rendering order of <SuspenseList revealOrder="backwards"> is changing. '+
32643259
'To be future compatible you must specify revealOrder="legacy_unstable-backwards" instead.',
@@ -3314,18 +3309,7 @@ function validateTailOptions(
33143309
constcacheKey=tailMode==null ? 'null' : tailMode;
33153310
if(!didWarnAboutTailOptions[cacheKey]){
33163311
if(tailMode==null){
3317-
if(
3318-
revealOrder==='forwards'||
3319-
revealOrder==='backwards'||
3320-
revealOrder==='unstable_legacy-backwards'
3321-
){
3322-
didWarnAboutTailOptions[cacheKey]=true;
3323-
console.error(
3324-
'The default for the <SuspenseList tail="..."> prop is changing. '+
3325-
'To be future compatible you must explictly specify either '+
3326-
'"visible" (the current default), "collapsed" or "hidden".',
3327-
);
3328-
}
3312+
// The default tail is now "hidden".
33293313
}elseif(
33303314
tailMode!=='visible'&&
33313315
tailMode!=='collapsed'&&
@@ -3338,14 +3322,15 @@ function validateTailOptions(
33383322
tailMode,
33393323
);
33403324
}elseif(
3325+
revealOrder!=null&&
33413326
revealOrder!=='forwards'&&
33423327
revealOrder!=='backwards'&&
33433328
revealOrder!=='unstable_legacy-backwards'
33443329
){
33453330
didWarnAboutTailOptions[cacheKey]=true;
33463331
console.error(
33473332
'<SuspenseList tail="%s" /> is only valid if revealOrder is '+
3348-
'"forwards" or "backwards". '+
3333+
'"forwards" (default) or "backwards". '+
33493334
'Did you mean to specify revealOrder="forwards"?',
33503335
tailMode,
33513336
);
@@ -3449,30 +3434,6 @@ function updateSuspenseListComponent(
34493434
workInProgress.memoizedState=null;
34503435
}else{
34513436
switch(revealOrder){
3452-
case'forwards': {
3453-
constlastContentRow=findLastContentRow(workInProgress.child);
3454-
lettail;
3455-
if(lastContentRow===null){
3456-
// The whole list is part of the tail.
3457-
// TODO: We could fast path by just rendering the tail now.
3458-
tail=workInProgress.child;
3459-
workInProgress.child=null;
3460-
}else{
3461-
// Disconnect the tail rows after the content row.
3462-
// We're going to render them separately later.
3463-
tail=lastContentRow.sibling;
3464-
lastContentRow.sibling=null;
3465-
}
3466-
initSuspenseListRenderState(
3467-
workInProgress,
3468-
false,// isBackwards
3469-
tail,
3470-
lastContentRow,
3471-
tailMode,
3472-
treeForkCount,
3473-
);
3474-
break;
3475-
}
34763437
case'backwards':
34773438
case'unstable_legacy-backwards': {
34783439
// We're going to find the first row that has existing content.
@@ -3517,10 +3478,37 @@ function updateSuspenseListComponent(
35173478
);
35183479
break;
35193480
}
3520-
default: {
3521-
// The default reveal order is the same as not having
3481+
case'independent': {
3482+
// The "independent" reveal order is the same as not having
35223483
// a boundary.
35233484
workInProgress.memoizedState=null;
3485+
break;
3486+
}
3487+
// The default is now forwards.
3488+
case'forwards':
3489+
default: {
3490+
constlastContentRow=findLastContentRow(workInProgress.child);
3491+
lettail;
3492+
if(lastContentRow===null){
3493+
// The whole list is part of the tail.
3494+
// TODO: We could fast path by just rendering the tail now.
3495+
tail=workInProgress.child;
3496+
workInProgress.child=null;
3497+
}else{
3498+
// Disconnect the tail rows after the content row.
3499+
// We're going to render them separately later.
3500+
tail=lastContentRow.sibling;
3501+
lastContentRow.sibling=null;
3502+
}
3503+
initSuspenseListRenderState(
3504+
workInProgress,
3505+
false,// isBackwards
3506+
tail,
3507+
lastContentRow,
3508+
tailMode,
3509+
treeForkCount,
3510+
);
3511+
break;
35243512
}
35253513
}
35263514
}

‎packages/react-reconciler/src/ReactFiberCompleteWork.js‎

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -698,7 +698,11 @@ function cutOffTailIfNeeded(
698698
return;
699699
}
700700
switch(renderState.tailMode){
701-
case'hidden': {
701+
case'visible': {
702+
// Everything should remain as it was.
703+
break;
704+
}
705+
case'collapsed': {
702706
// Any insertions at the end of the tail list after this point
703707
// should be invisible. If there are already mounted boundaries
704708
// anything before them are not considered for collapsing.
@@ -716,15 +720,23 @@ function cutOffTailIfNeeded(
716720
// last rendered item.
717721
if(lastTailNode===null){
718722
// All remaining items in the tail are insertions.
719-
renderState.tail=null;
723+
if(!hasRenderedATailFallback&&renderState.tail!==null){
724+
// We suspended during the head. We want to show at least one
725+
// row at the tail. So we'll keep on and cut off the rest.
726+
renderState.tail.sibling=null;
727+
}else{
728+
renderState.tail=null;
729+
}
720730
}else{
721731
// Detach the insertion after the last node that was already
722732
// inserted.
723733
lastTailNode.sibling=null;
724734
}
725735
break;
726736
}
727-
case'collapsed': {
737+
// Hidden is now the default.
738+
case'hidden':
739+
default: {
728740
// Any insertions at the end of the tail list after this point
729741
// should be invisible. If there are already mounted boundaries
730742
// anything before them are not considered for collapsing.
@@ -742,13 +754,7 @@ function cutOffTailIfNeeded(
742754
// last rendered item.
743755
if(lastTailNode===null){
744756
// All remaining items in the tail are insertions.
745-
if(!hasRenderedATailFallback&&renderState.tail!==null){
746-
// We suspended during the head. We want to show at least one
747-
// row at the tail. So we'll keep on and cut off the rest.
748-
renderState.tail.sibling=null;
749-
}else{
750-
renderState.tail=null;
751-
}
757+
renderState.tail=null;
752758
}else{
753759
// Detach the insertion after the last node that was already
754760
// inserted.
@@ -1795,7 +1801,8 @@ function completeWork(
17951801
// This might have been modified.
17961802
if(
17971803
renderState.tail===null&&
1798-
renderState.tailMode==='hidden'&&
1804+
renderState.tailMode!=='collapsed'&&
1805+
renderState.tailMode!=='visible'&&
17991806
!renderedTail.alternate&&
18001807
!getIsHydrating()// We don't cut it if we're hydrating.
18011808
){

‎packages/react-reconciler/src/ReactFiberSuspenseComponent.js‎

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,7 @@ export function findFirstSuspended(row: Fiber): null | Fiber {
7979
node.tag===SuspenseListComponent&&
8080
// Independent revealOrder can't be trusted because it doesn't
8181
// keep track of whether it suspended or not.
82-
(node.memoizedProps.revealOrder==='forwards'||
83-
node.memoizedProps.revealOrder==='backwards'||
84-
node.memoizedProps.revealOrder==='unstable_legacy-backwards'||
85-
node.memoizedProps.revealOrder==='together')
82+
node.memoizedProps.revealOrder!=='independent'
8683
){
8784
constdidSuspend=(node.flags&DidCapture)!==NoFlags;
8885
if(didSuspend){

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Switch the default revealOrder to "forwards" and tail "hidden" on Sus… · react/react@26cf280 · GitHub
Skip to content

Commit 26cf280

Browse files
authored
Switch the default revealOrder to "forwards" and tail "hidden" on SuspenseList (#35018)
We have warned about this for a while now so we can make the switch. Often when you reach for SuspenseList, you mean forwards. It doesn't make sense to have the default to just be a noop. While "together" is another useful mode that's more like a Group so isn't so associated with the default as List. So we're switching it. However, tail=hidden isn't as obvious of a default it does allow for a convenient pattern for streaming in list of items by default. This doesn't yet switch the rendering order of "backwards". That's coming in a follow up.
1 parent c9ddee7 commit 26cf280

7 files changed

Lines changed: 142 additions & 125 deletions

File tree

‎packages/react-dom/src/__tests__/ReactDOMFizzSuspenseList-test.js‎

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ describe('ReactDOMFizzSuspenseList', () => {
134134
}
135135

136136
// @gate enableSuspenseList
137-
it('shows content independently by default',async()=>{
137+
it('shows content forwards by default',async()=>{
138138
constA=createAsyncText('A');
139139
constB=createAsyncText('B');
140140
constC=createAsyncText('C');
@@ -157,31 +157,38 @@ describe('ReactDOMFizzSuspenseList', () => {
157157
);
158158
}
159159

160-
awaitA.resolve();
160+
awaitC.resolve();
161161

162162
awaitserverAct(async()=>{
163163
const{pipe}=ReactDOMFizzServer.renderToPipeableStream(<Foo/>);
164164
pipe(writable);
165165
});
166166

167-
assertLog(['A','Suspend! [B]','Suspend! [C]','Loading B','Loading C']);
167+
assertLog([
168+
'Suspend! [A]',
169+
'Suspend! [B]',// TODO: Defer rendering the content after fallback if previous suspended,
170+
'C',
171+
'Loading A',
172+
'Loading B',
173+
'Loading C',
174+
]);
168175

169176
expect(getVisibleChildren(container)).toEqual(
170177
<div>
171-
<span>A</span>
178+
<span>Loading A</span>
172179
<span>Loading B</span>
173180
<span>Loading C</span>
174181
</div>,
175182
);
176183

177-
awaitserverAct(()=>C.resolve());
178-
assertLog(['C']);
184+
awaitserverAct(()=>A.resolve());
185+
assertLog(['A']);
179186

180187
expect(getVisibleChildren(container)).toEqual(
181188
<div>
182189
<span>A</span>
183190
<span>Loading B</span>
184-
<span>C</span>
191+
<span>Loading C</span>
185192
</div>,
186193
);
187194

‎packages/react-reconciler/src/ReactChildFiber.js‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2104,7 +2104,8 @@ export function validateSuspenseListChildren(
21042104
){
21052105
if(__DEV__){
21062106
if(
2107-
(revealOrder==='forwards'||
2107+
(revealOrder==null||
2108+
revealOrder==='forwards'||
21082109
revealOrder==='backwards'||
21092110
revealOrder==='unstable_legacy-backwards')&&
21102111
children!==undefined&&

‎packages/react-reconciler/src/ReactFiberBeginWork.js‎

Lines changed: 34 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -3245,20 +3245,15 @@ function validateRevealOrder(revealOrder: SuspenseListRevealOrder) {
32453245
if(__DEV__){
32463246
constcacheKey=revealOrder==null ? 'null' : revealOrder;
32473247
if(
3248+
revealOrder!=null&&
32483249
revealOrder!=='forwards'&&
32493250
revealOrder!=='unstable_legacy-backwards'&&
32503251
revealOrder!=='together'&&
32513252
revealOrder!=='independent'&&
32523253
!didWarnAboutRevealOrder[cacheKey]
32533254
){
32543255
didWarnAboutRevealOrder[cacheKey]=true;
3255-
if(revealOrder==null){
3256-
console.error(
3257-
'The default for the <SuspenseList revealOrder="..."> prop is changing. '+
3258-
'To be future compatible you must explictly specify either '+
3259-
'"independent" (the current default), "together", "forwards" or "legacy_unstable-backwards".',
3260-
);
3261-
}elseif(revealOrder==='backwards'){
3256+
if(revealOrder==='backwards'){
32623257
console.error(
32633258
'The rendering order of <SuspenseList revealOrder="backwards"> is changing. '+
32643259
'To be future compatible you must specify revealOrder="legacy_unstable-backwards" instead.',
@@ -3314,18 +3309,7 @@ function validateTailOptions(
33143309
constcacheKey=tailMode==null ? 'null' : tailMode;
33153310
if(!didWarnAboutTailOptions[cacheKey]){
33163311
if(tailMode==null){
3317-
if(
3318-
revealOrder==='forwards'||
3319-
revealOrder==='backwards'||
3320-
revealOrder==='unstable_legacy-backwards'
3321-
){
3322-
didWarnAboutTailOptions[cacheKey]=true;
3323-
console.error(
3324-
'The default for the <SuspenseList tail="..."> prop is changing. '+
3325-
'To be future compatible you must explictly specify either '+
3326-
'"visible" (the current default), "collapsed" or "hidden".',
3327-
);
3328-
}
3312+
// The default tail is now "hidden".
33293313
}elseif(
33303314
tailMode!=='visible'&&
33313315
tailMode!=='collapsed'&&
@@ -3338,14 +3322,15 @@ function validateTailOptions(
33383322
tailMode,
33393323
);
33403324
}elseif(
3325+
revealOrder!=null&&
33413326
revealOrder!=='forwards'&&
33423327
revealOrder!=='backwards'&&
33433328
revealOrder!=='unstable_legacy-backwards'
33443329
){
33453330
didWarnAboutTailOptions[cacheKey]=true;
33463331
console.error(
33473332
'<SuspenseList tail="%s" /> is only valid if revealOrder is '+
3348-
'"forwards" or "backwards". '+
3333+
'"forwards" (default) or "backwards". '+
33493334
'Did you mean to specify revealOrder="forwards"?',
33503335
tailMode,
33513336
);
@@ -3449,30 +3434,6 @@ function updateSuspenseListComponent(
34493434
workInProgress.memoizedState=null;
34503435
}else{
34513436
switch(revealOrder){
3452-
case'forwards': {
3453-
constlastContentRow=findLastContentRow(workInProgress.child);
3454-
lettail;
3455-
if(lastContentRow===null){
3456-
// The whole list is part of the tail.
3457-
// TODO: We could fast path by just rendering the tail now.
3458-
tail=workInProgress.child;
3459-
workInProgress.child=null;
3460-
}else{
3461-
// Disconnect the tail rows after the content row.
3462-
// We're going to render them separately later.
3463-
tail=lastContentRow.sibling;
3464-
lastContentRow.sibling=null;
3465-
}
3466-
initSuspenseListRenderState(
3467-
workInProgress,
3468-
false,// isBackwards
3469-
tail,
3470-
lastContentRow,
3471-
tailMode,
3472-
treeForkCount,
3473-
);
3474-
break;
3475-
}
34763437
case'backwards':
34773438
case'unstable_legacy-backwards': {
34783439
// We're going to find the first row that has existing content.
@@ -3517,10 +3478,37 @@ function updateSuspenseListComponent(
35173478
);
35183479
break;
35193480
}
3520-
default: {
3521-
// The default reveal order is the same as not having
3481+
case'independent': {
3482+
// The "independent" reveal order is the same as not having
35223483
// a boundary.
35233484
workInProgress.memoizedState=null;
3485+
break;
3486+
}
3487+
// The default is now forwards.
3488+
case'forwards':
3489+
default: {
3490+
constlastContentRow=findLastContentRow(workInProgress.child);
3491+
lettail;
3492+
if(lastContentRow===null){
3493+
// The whole list is part of the tail.
3494+
// TODO: We could fast path by just rendering the tail now.
3495+
tail=workInProgress.child;
3496+
workInProgress.child=null;
3497+
}else{
3498+
// Disconnect the tail rows after the content row.
3499+
// We're going to render them separately later.
3500+
tail=lastContentRow.sibling;
3501+
lastContentRow.sibling=null;
3502+
}
3503+
initSuspenseListRenderState(
3504+
workInProgress,
3505+
false,// isBackwards
3506+
tail,
3507+
lastContentRow,
3508+
tailMode,
3509+
treeForkCount,
3510+
);
3511+
break;
35243512
}
35253513
}
35263514
}

‎packages/react-reconciler/src/ReactFiberCompleteWork.js‎

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -698,7 +698,11 @@ function cutOffTailIfNeeded(
698698
return;
699699
}
700700
switch(renderState.tailMode){
701-
case'hidden': {
701+
case'visible': {
702+
// Everything should remain as it was.
703+
break;
704+
}
705+
case'collapsed': {
702706
// Any insertions at the end of the tail list after this point
703707
// should be invisible. If there are already mounted boundaries
704708
// anything before them are not considered for collapsing.
@@ -716,15 +720,23 @@ function cutOffTailIfNeeded(
716720
// last rendered item.
717721
if(lastTailNode===null){
718722
// All remaining items in the tail are insertions.
719-
renderState.tail=null;
723+
if(!hasRenderedATailFallback&&renderState.tail!==null){
724+
// We suspended during the head. We want to show at least one
725+
// row at the tail. So we'll keep on and cut off the rest.
726+
renderState.tail.sibling=null;
727+
}else{
728+
renderState.tail=null;
729+
}
720730
}else{
721731
// Detach the insertion after the last node that was already
722732
// inserted.
723733
lastTailNode.sibling=null;
724734
}
725735
break;
726736
}
727-
case'collapsed': {
737+
// Hidden is now the default.
738+
case'hidden':
739+
default: {
728740
// Any insertions at the end of the tail list after this point
729741
// should be invisible. If there are already mounted boundaries
730742
// anything before them are not considered for collapsing.
@@ -742,13 +754,7 @@ function cutOffTailIfNeeded(
742754
// last rendered item.
743755
if(lastTailNode===null){
744756
// All remaining items in the tail are insertions.
745-
if(!hasRenderedATailFallback&&renderState.tail!==null){
746-
// We suspended during the head. We want to show at least one
747-
// row at the tail. So we'll keep on and cut off the rest.
748-
renderState.tail.sibling=null;
749-
}else{
750-
renderState.tail=null;
751-
}
757+
renderState.tail=null;
752758
}else{
753759
// Detach the insertion after the last node that was already
754760
// inserted.
@@ -1795,7 +1801,8 @@ function completeWork(
17951801
// This might have been modified.
17961802
if(
17971803
renderState.tail===null&&
1798-
renderState.tailMode==='hidden'&&
1804+
renderState.tailMode!=='collapsed'&&
1805+
renderState.tailMode!=='visible'&&
17991806
!renderedTail.alternate&&
18001807
!getIsHydrating()// We don't cut it if we're hydrating.
18011808
){

‎packages/react-reconciler/src/ReactFiberSuspenseComponent.js‎

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,7 @@ export function findFirstSuspended(row: Fiber): null | Fiber {
7979
node.tag===SuspenseListComponent&&
8080
// Independent revealOrder can't be trusted because it doesn't
8181
// keep track of whether it suspended or not.
82-
(node.memoizedProps.revealOrder==='forwards'||
83-
node.memoizedProps.revealOrder==='backwards'||
84-
node.memoizedProps.revealOrder==='unstable_legacy-backwards'||
85-
node.memoizedProps.revealOrder==='together')
82+
node.memoizedProps.revealOrder!=='independent'
8683
){
8784
constdidSuspend=(node.flags&DidCapture)!==NoFlags;
8885
if(didSuspend){

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Switch the default revealOrder to "forwards" and tail "hidden" on Sus… · react/react@26cf280 · GitHub
Skip to content

Commit 26cf280

Browse files
authored
Switch the default revealOrder to "forwards" and tail "hidden" on SuspenseList (#35018)
We have warned about this for a while now so we can make the switch. Often when you reach for SuspenseList, you mean forwards. It doesn't make sense to have the default to just be a noop. While "together" is another useful mode that's more like a Group so isn't so associated with the default as List. So we're switching it. However, tail=hidden isn't as obvious of a default it does allow for a convenient pattern for streaming in list of items by default. This doesn't yet switch the rendering order of "backwards". That's coming in a follow up.
1 parent c9ddee7 commit 26cf280

7 files changed

Lines changed: 142 additions & 125 deletions

File tree

‎packages/react-dom/src/__tests__/ReactDOMFizzSuspenseList-test.js‎

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ describe('ReactDOMFizzSuspenseList', () => {
134134
}
135135

136136
// @gate enableSuspenseList
137-
it('shows content independently by default',async()=>{
137+
it('shows content forwards by default',async()=>{
138138
constA=createAsyncText('A');
139139
constB=createAsyncText('B');
140140
constC=createAsyncText('C');
@@ -157,31 +157,38 @@ describe('ReactDOMFizzSuspenseList', () => {
157157
);
158158
}
159159

160-
awaitA.resolve();
160+
awaitC.resolve();
161161

162162
awaitserverAct(async()=>{
163163
const{pipe}=ReactDOMFizzServer.renderToPipeableStream(<Foo/>);
164164
pipe(writable);
165165
});
166166

167-
assertLog(['A','Suspend! [B]','Suspend! [C]','Loading B','Loading C']);
167+
assertLog([
168+
'Suspend! [A]',
169+
'Suspend! [B]',// TODO: Defer rendering the content after fallback if previous suspended,
170+
'C',
171+
'Loading A',
172+
'Loading B',
173+
'Loading C',
174+
]);
168175

169176
expect(getVisibleChildren(container)).toEqual(
170177
<div>
171-
<span>A</span>
178+
<span>Loading A</span>
172179
<span>Loading B</span>
173180
<span>Loading C</span>
174181
</div>,
175182
);
176183

177-
awaitserverAct(()=>C.resolve());
178-
assertLog(['C']);
184+
awaitserverAct(()=>A.resolve());
185+
assertLog(['A']);
179186

180187
expect(getVisibleChildren(container)).toEqual(
181188
<div>
182189
<span>A</span>
183190
<span>Loading B</span>
184-
<span>C</span>
191+
<span>Loading C</span>
185192
</div>,
186193
);
187194

‎packages/react-reconciler/src/ReactChildFiber.js‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2104,7 +2104,8 @@ export function validateSuspenseListChildren(
21042104
){
21052105
if(__DEV__){
21062106
if(
2107-
(revealOrder==='forwards'||
2107+
(revealOrder==null||
2108+
revealOrder==='forwards'||
21082109
revealOrder==='backwards'||
21092110
revealOrder==='unstable_legacy-backwards')&&
21102111
children!==undefined&&

‎packages/react-reconciler/src/ReactFiberBeginWork.js‎

Lines changed: 34 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -3245,20 +3245,15 @@ function validateRevealOrder(revealOrder: SuspenseListRevealOrder) {
32453245
if(__DEV__){
32463246
constcacheKey=revealOrder==null ? 'null' : revealOrder;
32473247
if(
3248+
revealOrder!=null&&
32483249
revealOrder!=='forwards'&&
32493250
revealOrder!=='unstable_legacy-backwards'&&
32503251
revealOrder!=='together'&&
32513252
revealOrder!=='independent'&&
32523253
!didWarnAboutRevealOrder[cacheKey]
32533254
){
32543255
didWarnAboutRevealOrder[cacheKey]=true;
3255-
if(revealOrder==null){
3256-
console.error(
3257-
'The default for the <SuspenseList revealOrder="..."> prop is changing. '+
3258-
'To be future compatible you must explictly specify either '+
3259-
'"independent" (the current default), "together", "forwards" or "legacy_unstable-backwards".',
3260-
);
3261-
}elseif(revealOrder==='backwards'){
3256+
if(revealOrder==='backwards'){
32623257
console.error(
32633258
'The rendering order of <SuspenseList revealOrder="backwards"> is changing. '+
32643259
'To be future compatible you must specify revealOrder="legacy_unstable-backwards" instead.',
@@ -3314,18 +3309,7 @@ function validateTailOptions(
33143309
constcacheKey=tailMode==null ? 'null' : tailMode;
33153310
if(!didWarnAboutTailOptions[cacheKey]){
33163311
if(tailMode==null){
3317-
if(
3318-
revealOrder==='forwards'||
3319-
revealOrder==='backwards'||
3320-
revealOrder==='unstable_legacy-backwards'
3321-
){
3322-
didWarnAboutTailOptions[cacheKey]=true;
3323-
console.error(
3324-
'The default for the <SuspenseList tail="..."> prop is changing. '+
3325-
'To be future compatible you must explictly specify either '+
3326-
'"visible" (the current default), "collapsed" or "hidden".',
3327-
);
3328-
}
3312+
// The default tail is now "hidden".
33293313
}elseif(
33303314
tailMode!=='visible'&&
33313315
tailMode!=='collapsed'&&
@@ -3338,14 +3322,15 @@ function validateTailOptions(
33383322
tailMode,
33393323
);
33403324
}elseif(
3325+
revealOrder!=null&&
33413326
revealOrder!=='forwards'&&
33423327
revealOrder!=='backwards'&&
33433328
revealOrder!=='unstable_legacy-backwards'
33443329
){
33453330
didWarnAboutTailOptions[cacheKey]=true;
33463331
console.error(
33473332
'<SuspenseList tail="%s" /> is only valid if revealOrder is '+
3348-
'"forwards" or "backwards". '+
3333+
'"forwards" (default) or "backwards". '+
33493334
'Did you mean to specify revealOrder="forwards"?',
33503335
tailMode,
33513336
);
@@ -3449,30 +3434,6 @@ function updateSuspenseListComponent(
34493434
workInProgress.memoizedState=null;
34503435
}else{
34513436
switch(revealOrder){
3452-
case'forwards': {
3453-
constlastContentRow=findLastContentRow(workInProgress.child);
3454-
lettail;
3455-
if(lastContentRow===null){
3456-
// The whole list is part of the tail.
3457-
// TODO: We could fast path by just rendering the tail now.
3458-
tail=workInProgress.child;
3459-
workInProgress.child=null;
3460-
}else{
3461-
// Disconnect the tail rows after the content row.
3462-
// We're going to render them separately later.
3463-
tail=lastContentRow.sibling;
3464-
lastContentRow.sibling=null;
3465-
}
3466-
initSuspenseListRenderState(
3467-
workInProgress,
3468-
false,// isBackwards
3469-
tail,
3470-
lastContentRow,
3471-
tailMode,
3472-
treeForkCount,
3473-
);
3474-
break;
3475-
}
34763437
case'backwards':
34773438
case'unstable_legacy-backwards': {
34783439
// We're going to find the first row that has existing content.
@@ -3517,10 +3478,37 @@ function updateSuspenseListComponent(
35173478
);
35183479
break;
35193480
}
3520-
default: {
3521-
// The default reveal order is the same as not having
3481+
case'independent': {
3482+
// The "independent" reveal order is the same as not having
35223483
// a boundary.
35233484
workInProgress.memoizedState=null;
3485+
break;
3486+
}
3487+
// The default is now forwards.
3488+
case'forwards':
3489+
default: {
3490+
constlastContentRow=findLastContentRow(workInProgress.child);
3491+
lettail;
3492+
if(lastContentRow===null){
3493+
// The whole list is part of the tail.
3494+
// TODO: We could fast path by just rendering the tail now.
3495+
tail=workInProgress.child;
3496+
workInProgress.child=null;
3497+
}else{
3498+
// Disconnect the tail rows after the content row.
3499+
// We're going to render them separately later.
3500+
tail=lastContentRow.sibling;
3501+
lastContentRow.sibling=null;
3502+
}
3503+
initSuspenseListRenderState(
3504+
workInProgress,
3505+
false,// isBackwards
3506+
tail,
3507+
lastContentRow,
3508+
tailMode,
3509+
treeForkCount,
3510+
);
3511+
break;
35243512
}
35253513
}
35263514
}

‎packages/react-reconciler/src/ReactFiberCompleteWork.js‎

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -698,7 +698,11 @@ function cutOffTailIfNeeded(
698698
return;
699699
}
700700
switch(renderState.tailMode){
701-
case'hidden': {
701+
case'visible': {
702+
// Everything should remain as it was.
703+
break;
704+
}
705+
case'collapsed': {
702706
// Any insertions at the end of the tail list after this point
703707
// should be invisible. If there are already mounted boundaries
704708
// anything before them are not considered for collapsing.
@@ -716,15 +720,23 @@ function cutOffTailIfNeeded(
716720
// last rendered item.
717721
if(lastTailNode===null){
718722
// All remaining items in the tail are insertions.
719-
renderState.tail=null;
723+
if(!hasRenderedATailFallback&&renderState.tail!==null){
724+
// We suspended during the head. We want to show at least one
725+
// row at the tail. So we'll keep on and cut off the rest.
726+
renderState.tail.sibling=null;
727+
}else{
728+
renderState.tail=null;
729+
}
720730
}else{
721731
// Detach the insertion after the last node that was already
722732
// inserted.
723733
lastTailNode.sibling=null;
724734
}
725735
break;
726736
}
727-
case'collapsed': {
737+
// Hidden is now the default.
738+
case'hidden':
739+
default: {
728740
// Any insertions at the end of the tail list after this point
729741
// should be invisible. If there are already mounted boundaries
730742
// anything before them are not considered for collapsing.
@@ -742,13 +754,7 @@ function cutOffTailIfNeeded(
742754
// last rendered item.
743755
if(lastTailNode===null){
744756
// All remaining items in the tail are insertions.
745-
if(!hasRenderedATailFallback&&renderState.tail!==null){
746-
// We suspended during the head. We want to show at least one
747-
// row at the tail. So we'll keep on and cut off the rest.
748-
renderState.tail.sibling=null;
749-
}else{
750-
renderState.tail=null;
751-
}
757+
renderState.tail=null;
752758
}else{
753759
// Detach the insertion after the last node that was already
754760
// inserted.
@@ -1795,7 +1801,8 @@ function completeWork(
17951801
// This might have been modified.
17961802
if(
17971803
renderState.tail===null&&
1798-
renderState.tailMode==='hidden'&&
1804+
renderState.tailMode!=='collapsed'&&
1805+
renderState.tailMode!=='visible'&&
17991806
!renderedTail.alternate&&
18001807
!getIsHydrating()// We don't cut it if we're hydrating.
18011808
){

‎packages/react-reconciler/src/ReactFiberSuspenseComponent.js‎

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,7 @@ export function findFirstSuspended(row: Fiber): null | Fiber {
7979
node.tag===SuspenseListComponent&&
8080
// Independent revealOrder can't be trusted because it doesn't
8181
// keep track of whether it suspended or not.
82-
(node.memoizedProps.revealOrder==='forwards'||
83-
node.memoizedProps.revealOrder==='backwards'||
84-
node.memoizedProps.revealOrder==='unstable_legacy-backwards'||
85-
node.memoizedProps.revealOrder==='together')
82+
node.memoizedProps.revealOrder!=='independent'
8683
){
8784
constdidSuspend=(node.flags&DidCapture)!==NoFlags;
8885
if(didSuspend){

0 commit comments

Comments
 (0)