@samitouri / QOS-React / commits / dd935ea178

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