Propagate the need to reset children to closest visible Fiber
This fixes the bug.
Dan Abramov committed
Apr 16, 2019 at 16:31 UTC
dd935ea178970646874a98adb7003f051cebef65
1 file changed
+44
-20
src/backend/renderer.js
+44
-20
@@ -861,7 +861,7 @@ export function attach(
861
}
862
}
863
864
- function recordChildOrderChange(fiber: Fiber) {
864
+ function recordResetChildren(fiber: Fiber) {
865
// The frontend only really cares about the displayName, key, and children.
866
// The first two don't really change, so we are only concerned with the order of children here.
867
// This is trickier than a simple comparison though, since certain types of fibers are filtered.
@@ -901,17 +901,18 @@ export function attach(
901
}
902
}
903
904
+ // Returns whether closest unfiltered fiber parent needs to reset its child list.
905
function updateFiberRecursively(
906
nextFiber: Fiber,
907
prevFiber: Fiber,
908
parentFiber: Fiber | null
908
- ) {
909
+ ): boolean {
910
if (__DEBUG__) {
911
debug('updateFiberRecursively()', nextFiber, parentFiber);
912
}
913
const shouldIncludeInTree = !shouldFilterFiber(nextFiber);
914
const isSuspense = nextFiber.tag === SuspenseComponent;
914
- let hasChildOrderChanged = false;
915
+ let shouldResetChildren = false;
916
// The behavior of timed-out Suspense trees is unique.
917
// Rather than unmount the timed out content (and possibly lose important state),
918
// React re-parents this content within a hidden Fragment while the fallback is showing.
@@ -931,11 +932,15 @@ export function attach(
932
// Note: We can't use nextFiber.child.sibling.alternate
933
// because the set is special and alternate may not exist.
934
const prevFallbackChildSet = prevFiber.child.sibling;
934
- updateFiberRecursively(
935
- nextFallbackChildSet,
936
- prevFallbackChildSet,
937
- nextFiber
938
- );
935
+ if (
936
+ updateFiberRecursively(
937
+ nextFallbackChildSet,
938
+ prevFallbackChildSet,
939
+ nextFiber
940
+ )
941
+ ) {
942
+ shouldResetChildren = true;
943
+ }
944
} else if (prevDidTimeout && !nextDidTimeOut) {
945
// Fallback -> Primary:
946
// 1. Unmount fallback set
@@ -943,6 +948,7 @@ export function attach(
948
// 2. Mount primary set
949
const nextPrimaryChildSet = nextFiber.child;
950
mountFiberRecursively(nextPrimaryChildSet, nextFiber, true);
951
+ shouldResetChildren = true;
952
} else if (!prevDidTimeout && nextDidTimeOut) {
953
// Primary -> Fallback:
954
// 1. Hide primary set
@@ -955,6 +961,7 @@ export function attach(
961
// 2. Mount fallback set
962
const nextFallbackChildSet = nextFiber.child.sibling;
963
mountFiberRecursively(nextFallbackChildSet, nextFiber, true);
964
+ shouldResetChildren = true;
965
} else if (!prevDidTimeout && !nextDidTimeOut) {
966
// Common case: Primary -> Primary.
967
// This is the same codepath as for non-Suspense fibers.
@@ -970,35 +977,42 @@ export function attach(
977
// We don't track deletions here because they are reported separately.
978
if (nextChild.alternate) {
979
const prevChild = nextChild.alternate;
973
- updateFiberRecursively(
974
- nextChild,
975
- prevChild,
976
- shouldIncludeInTree ? nextFiber : parentFiber
977
- );
980
+ if (
981
+ updateFiberRecursively(
982
+ nextChild,
983
+ prevChild,
984
+ shouldIncludeInTree ? nextFiber : parentFiber
985
+ )
986
+ ) {
987
+ // If a nested tree child order changed but it can't handle its own
988
+ // child order invalidation (e.g. because it's filtered out like host nodes),
989
+ // propagate the need to reset child order upwards to this Fiber.
990
+ shouldResetChildren = true;
991
+ }
992
// However we also keep track if the order of the children matches
993
// the previous order. They are always different referentially, but
994
// if the instances line up conceptually we'll want to know that.
995
if (prevChild !== prevChildAtSameIndex) {
982
- hasChildOrderChanged = true;
996
+ shouldResetChildren = true;
997
}
998
} else {
999
mountFiberRecursively(
1000
nextChild,
1001
shouldIncludeInTree ? nextFiber : parentFiber
1002
);
989
- hasChildOrderChanged = true;
1003
+ shouldResetChildren = true;
1004
}
1005
// Try the next child.
1006
nextChild = nextChild.sibling;
1007
// Advance the pointer in the previous list so that we can
1008
// keep comparing if they line up.
995
- if (!hasChildOrderChanged && prevChildAtSameIndex != null) {
1009
+ if (!shouldResetChildren && prevChildAtSameIndex !== null) {
1010
prevChildAtSameIndex = prevChildAtSameIndex.sibling;
1011
}
1012
}
1013
// If we have no more children, but used to, they don't line up.
1000
- if (!hasChildOrderChanged && prevChildAtSameIndex != null) {
1001
- hasChildOrderChanged = true;
1014
+ if (prevChildAtSameIndex !== null) {
1015
+ shouldResetChildren = true;
1016
}
1017
}
1018
}
@@ -1007,9 +1021,19 @@ export function attach(
1021
if (isProfilingSupported) {
1022
recordTreeDuration(nextFiber);
1023
}
1010
- if (hasChildOrderChanged) {
1011
- recordChildOrderChange(nextFiber);
1024
+ }
1025
+ if (shouldResetChildren) {
1026
+ if (shouldIncludeInTree) {
1027
+ recordResetChildren(nextFiber);
1028
+ // We've handled the child order change for this Fiber.
1029
+ // Since it's included, there's no need to invalidate parent child order.
1030
+ return false;
1031
+ } else {
1032
+ // Let the closest unfiltered parent Fiber reset its child order instead.
1033
+ return true;
1034
}
1035
+ } else {
1036
+ return false;
1037
}
1038
}
1039