3247
if (
3248
revealOrder != null &&
3249
revealOrder !== 'forwards' &&
3250
+ revealOrder !== 'backwards' &&
3251
revealOrder !== 'unstable_legacy-backwards' &&
3252
revealOrder !== 'together' &&
3253
revealOrder !== 'independent' &&
3254
!didWarnAboutRevealOrder[cacheKey]
3255
) {
3256
didWarnAboutRevealOrder[cacheKey] = true;
3256
- if (revealOrder === 'backwards') {
3257
- console.error(
3258
- 'The rendering order of <SuspenseList revealOrder="backwards"> is changing. ' +
3259
- 'To be future compatible you must specify revealOrder="legacy_unstable-backwards" instead.',
3260
- );
3261
- } else if (typeof revealOrder === 'string') {
3257
+ if (typeof revealOrder === 'string') {
3258
switch (revealOrder.toLowerCase()) {
3259
case 'together':
3260
case 'forwards':
3367
}
3368
}
3369
3370
+function reverseChildren(fiber: Fiber): void {
3371
+ let row = fiber.child;
3372
+ fiber.child = null;
3373
+ while (row !== null) {
3374
+ const nextRow = row.sibling;
3375
+ row.sibling = fiber.child;
3376
+ fiber.child = row;
3377
+ row = nextRow;
3378
+ }
3379
+}
3380
+
3381
// This can end up rendering this component multiple passes.
3382
// The first pass splits the children fibers into two sets. A head and tail.
3383
// We first render the head. If anything is in fallback state, we do another
3416
validateTailOptions(tailMode, revealOrder);
3417
validateSuspenseListChildren(newChildren, revealOrder);
3418
3412
- reconcileChildren(current, workInProgress, newChildren, renderLanes);
3419
+ if (revealOrder === 'backwards' && current !== null) {
3420
+ // For backwards the current mounted set will be backwards. Reconciling against it
3421
+ // will lead to mismatches and reorders. We need to swap the original set first
3422
+ // and then restore it afterwards.
3423
+ reverseChildren(current);
3424
+ reconcileChildren(current, workInProgress, newChildren, renderLanes);
3425
+ reverseChildren(current);
3426
+ } else {
3427
+ reconcileChildren(current, workInProgress, newChildren, renderLanes);
3428
+ }
3429
// Read how many children forks this set pushed so we can push it every time we retry.
3430
const treeForkCount = getIsHydrating() ? getForksAtLevel(workInProgress) : 0;
3431
3450
workInProgress.memoizedState = null;
3451
} else {
3452
switch (revealOrder) {
3437
- case 'backwards':
3453
+ case 'backwards': {
3454
+ // We're going to find the first row that has existing content.
3455
+ // We are also going to reverse the order of anything in the existing content
3456
+ // since we want to actually render them backwards from the reconciled set.
3457
+ // The tail is left in order, because it'll be added to the front as we
3458
+ // complete each item.
3459
+ const lastContentRow = findLastContentRow(workInProgress.child);
3460
+ let tail;
3461
+ if (lastContentRow === null) {
3462
+ // The whole list is part of the tail.
3463
+ tail = workInProgress.child;
3464
+ workInProgress.child = null;
3465
+ } else {
3466
+ // Disconnect the tail rows after the content row.
3467
+ // We're going to render them separately later in reverse order.
3468
+ tail = lastContentRow.sibling;
3469
+ lastContentRow.sibling = null;
3470
+ // We have to now reverse the main content so it renders backwards too.
3471
+ reverseChildren(workInProgress);
3472
+ }
3473
+ // TODO: If workInProgress.child is null, we can continue on the tail immediately.
3474
+ initSuspenseListRenderState(
3475
+ workInProgress,
3476
+ true, // isBackwards
3477
+ tail,
3478
+ null, // last
3479
+ tailMode,
3480
+ treeForkCount,
3481
+ );
3482
+ break;
3483
+ }
3484
case 'unstable_legacy-backwards': {
3485
// We're going to find the first row that has existing content.
3486
// At the same time we're going to reverse the list of everything