717
? getFiberIDUnsafe(parentFiber) || '<no-id>'
718
: '';
719
720
- console.log(
720
+ console.groupCollapsed(
721
`[renderer] %c${name} %c${displayName} (${maybeID}) %c${
722
parentFiber ? `${parentDisplayName} (${maybeParentID})` : ''
723
} %c${extraString}`,
726
'color: purple;',
727
'color: black;',
728
);
729
+ console.log(
730
+ new Error().stack
731
+ .split('\n')
732
+ .slice(1)
733
+ .join('\n'),
734
+ );
735
+ console.groupEnd();
736
}
737
};
738
1003
}
1004
}
1005
1006
+ let didGenerateID = false;
1007
if (id === null) {
1008
+ didGenerateID = true;
1009
id = getUID();
1010
}
1011
1028
}
1029
}
1030
1031
+ if (__DEBUG__) {
1032
+ if (didGenerateID) {
1033
+ debug(
1034
+ 'getOrGenerateFiberID()',
1035
+ fiber,
1036
+ fiber.return,
1037
+ 'Generated a new UID',
1038
+ );
1039
+ }
1040
+ }
1041
+
1042
return refinedID;
1043
}
1044
1070
// Removes a Fiber (and its alternate) from the Maps used to track their id.
1071
// This method should always be called when a Fiber is unmounting.
1072
function untrackFiberID(fiber: Fiber) {
1053
- const fiberID = getFiberIDUnsafe(fiber);
1054
- if (fiberID !== null) {
1055
- idToArbitraryFiberMap.delete(fiberID);
1073
+ if (__DEBUG__) {
1074
+ debug('untrackFiberID()', fiber, fiber.return, 'schedule after delay');
1075
}
1076
1058
- fiberToIDMap.delete(fiber);
1077
+ // Untrack Fibers after a slight delay in order to support a Fast Refresh edge case:
1078
+ // 1. Component type is updated and Fast Refresh schedules an update+remount.
1079
+ // 2. flushPendingErrorsAndWarningsAfterDelay() runs, sees the old Fiber is no longer mounted
1080
+ // (it's been disconnected by Fast Refresh), and calls untrackFiberID() to clear it from the Map.
1081
+ // 3. React flushes pending passive effects before it runs the next render,
1082
+ // which logs an error or warning, which causes a new ID to be generated for this Fiber.
1083
+ // 4. DevTools now tries to unmount the old Component with the new ID.
1084
+ //
1085
+ // The underlying problem here is the premature clearing of the Fiber ID,
1086
+ // but DevTools has no way to detect that a given Fiber has been scheduled for Fast Refresh.
1087
+ // (The "_debugNeedsRemount" flag won't necessarily be set.)
1088
+ //
1089
+ // The best we can do is to delay untracking by a small amount,
1090
+ // and give React time to process the Fast Refresh delay.
1091
1060
- const {alternate} = fiber;
1061
- if (alternate !== null) {
1062
- fiberToIDMap.delete(alternate);
1092
+ untrackFibersSet.add(fiber);
1093
+
1094
+ if (untrackFibersTimeoutID === null) {
1095
+ untrackFibersTimeoutID = setTimeout(untrackFibers, 1000);
1096
}
1097
}
1098
1099
+ const untrackFibersSet: Set<Fiber> = new Set();
1100
+ let untrackFibersTimeoutID: TimeoutID | null = null;
1101
+
1102
+ function untrackFibers() {
1103
+ if (untrackFibersTimeoutID !== null) {
1104
+ clearTimeout(untrackFibersTimeoutID);
1105
+ untrackFibersTimeoutID = null;
1106
+ }
1107
+
1108
+ untrackFibersSet.forEach(fiber => {
1109
+ const fiberID = getFiberIDUnsafe(fiber);
1110
+ if (fiberID !== null) {
1111
+ idToArbitraryFiberMap.delete(fiberID);
1112
+
1113
+ // Also clear any errors/warnings associated with this fiber.
1114
+ clearErrorsForFiberID(fiberID);
1115
+ clearWarningsForFiberID(fiberID);
1116
+ }
1117
+
1118
+ fiberToIDMap.delete(fiber);
1119
+
1120
+ const {alternate} = fiber;
1121
+ if (alternate !== null) {
1122
+ fiberToIDMap.delete(alternate);
1123
+ }
1124
+ });
1125
+ untrackFibersSet.clear();
1126
+ }
1127
+
1128
function getChangeDescription(
1129
prevFiber: Fiber | null,
1130
nextFiber: Fiber,
1672
}
1673
1674
function recordMount(fiber: Fiber, parentFiber: Fiber | null) {
1675
+ const isRoot = fiber.tag === HostRoot;
1676
+ const id = getOrGenerateFiberID(fiber);
1677
+
1678
if (__DEBUG__) {
1679
debug('recordMount()', fiber, parentFiber);
1680
}
1681
1617
- const isRoot = fiber.tag === HostRoot;
1618
- const id = getOrGenerateFiberID(fiber);
1619
-
1682
const hasOwnerMetadata = fiber.hasOwnProperty('_debugOwner');
1683
const isProfilingSupported = fiber.hasOwnProperty('treeBaseDuration');
1684
1810
// This reduces the chance of stack overflow for wide trees (e.g. lists with many items).
1811
let fiber: Fiber | null = firstChild;
1812
while (fiber !== null) {
1813
+ // Generate an ID even for filtered Fibers, in case it's needed later (e.g. for Profiling).
1814
+ getOrGenerateFiberID(fiber);
1815
+
1816
if (__DEBUG__) {
1817
debug('mountFiberRecursively()', fiber, parentFiber);
1818
}
1826
const shouldIncludeInTree = !shouldFilterFiber(fiber);
1827
if (shouldIncludeInTree) {
1828
recordMount(fiber, parentFiber);
1764
- } else {
1765
- // Generate an ID even for filtered Fibers, in case it's needed later (e.g. for Profiling).
1766
- getOrGenerateFiberID(fiber);
1829
}
1830
1831
if (traceUpdatesEnabled) {
2070
parentFiber: Fiber | null,
2071
traceNearestHostComponentUpdate: boolean,
2072
): boolean {
2073
+ const id = getOrGenerateFiberID(nextFiber);
2074
+
2075
if (__DEBUG__) {
2076
debug('updateFiberRecursively()', nextFiber, parentFiber);
2077
}
2078
2015
- const id = getOrGenerateFiberID(nextFiber);
2016
-
2079
if (traceUpdatesEnabled) {
2080
const elementType = getElementTypeForFiber(nextFiber);
2081
if (traceNearestHostComponentUpdate) {
2384
const current = root.current;
2385
const alternate = current.alternate;
2386
2387
+ // Flush any pending Fibers that we are untracking before processing the new commit.
2388
+ // If we don't do this, we might end up double-deleting Fibers in some cases (like Legacy Suspense).
2389
+ untrackFibers();
2390
+
2391
currentRootID = getOrGenerateFiberID(current);
2392
2393
// Before the traversals, remember to start tracking