mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[Fresh] Fix an infinite loop in an edge case (#17414)
* [Fresh] Fix an infinite loop in an edge case * Make it work in IE11
This commit is contained in:
+29
-4
@@ -154,6 +154,22 @@ function resolveFamily(type) {
|
||||
return updatedFamiliesByType.get(type);
|
||||
}
|
||||
|
||||
// If we didn't care about IE11, we could use new Map/Set(iterable).
|
||||
function cloneMap<K, V>(map: Map<K, V>): Map<K, V> {
|
||||
let clone = new Map();
|
||||
map.forEach((value, key) => {
|
||||
clone.set(key, value);
|
||||
});
|
||||
return clone;
|
||||
}
|
||||
function cloneSet<T>(set: Set<T>): Set<T> {
|
||||
let clone = new Set();
|
||||
set.forEach(value => {
|
||||
clone.add(value);
|
||||
});
|
||||
return clone;
|
||||
}
|
||||
|
||||
export function performReactRefresh(): RefreshUpdate | null {
|
||||
if (__DEV__) {
|
||||
if (pendingUpdates.length === 0) {
|
||||
@@ -195,8 +211,17 @@ export function performReactRefresh(): RefreshUpdate | null {
|
||||
|
||||
let didError = false;
|
||||
let firstError = null;
|
||||
failedRoots.forEach((element, root) => {
|
||||
const helpers = helpersByRoot.get(root);
|
||||
|
||||
// We snapshot maps and sets that are mutated during commits.
|
||||
// If we don't do this, there is a risk they will be mutated while
|
||||
// we iterate over them. For example, trying to recover a failed root
|
||||
// may cause another root to be added to the failed list -- an infinite loop.
|
||||
let failedRootsSnapshot = cloneMap(failedRoots);
|
||||
let mountedRootsSnapshot = cloneSet(mountedRoots);
|
||||
let helpersByRootSnapshot = cloneMap(helpersByRoot);
|
||||
|
||||
failedRootsSnapshot.forEach((element, root) => {
|
||||
const helpers = helpersByRootSnapshot.get(root);
|
||||
if (helpers === undefined) {
|
||||
throw new Error(
|
||||
'Could not find helpers for a root. This is a bug in React Refresh.',
|
||||
@@ -212,8 +237,8 @@ export function performReactRefresh(): RefreshUpdate | null {
|
||||
// Keep trying other roots.
|
||||
}
|
||||
});
|
||||
mountedRoots.forEach(root => {
|
||||
const helpers = helpersByRoot.get(root);
|
||||
mountedRootsSnapshot.forEach(root => {
|
||||
const helpers = helpersByRootSnapshot.get(root);
|
||||
if (helpers === undefined) {
|
||||
throw new Error(
|
||||
'Could not find helpers for a root. This is a bug in React Refresh.',
|
||||
|
||||
@@ -2909,6 +2909,61 @@ describe('ReactFresh', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('regression test: does not get into an infinite loop', () => {
|
||||
if (__DEV__) {
|
||||
let containerA = document.createElement('div');
|
||||
let containerB = document.createElement('div');
|
||||
|
||||
// Initially, nothing interesting.
|
||||
let RootAV1 = () => {
|
||||
return 'A1';
|
||||
};
|
||||
$RefreshReg$(RootAV1, 'RootA');
|
||||
let RootBV1 = () => {
|
||||
return 'B1';
|
||||
};
|
||||
$RefreshReg$(RootBV1, 'RootB');
|
||||
|
||||
act(() => {
|
||||
ReactDOM.render(<RootAV1 />, containerA);
|
||||
ReactDOM.render(<RootBV1 />, containerB);
|
||||
});
|
||||
expect(containerA.innerHTML).toBe('A1');
|
||||
expect(containerB.innerHTML).toBe('B1');
|
||||
|
||||
// Then make the first root fail.
|
||||
let RootAV2 = () => {
|
||||
throw new Error('A2!');
|
||||
};
|
||||
$RefreshReg$(RootAV2, 'RootA');
|
||||
expect(() => ReactFreshRuntime.performReactRefresh()).toThrow('A2!');
|
||||
expect(containerA.innerHTML).toBe('');
|
||||
expect(containerB.innerHTML).toBe('B1');
|
||||
|
||||
// Then patch the first root, but make it fail in the commit phase.
|
||||
// This used to trigger an infinite loop due to a list of failed roots
|
||||
// being mutated while it was being iterated on.
|
||||
let RootAV3 = () => {
|
||||
React.useLayoutEffect(() => {
|
||||
throw new Error('A3!');
|
||||
}, []);
|
||||
return 'A3';
|
||||
};
|
||||
$RefreshReg$(RootAV3, 'RootA');
|
||||
expect(() => ReactFreshRuntime.performReactRefresh()).toThrow('A3!');
|
||||
expect(containerA.innerHTML).toBe('');
|
||||
expect(containerB.innerHTML).toBe('B1');
|
||||
|
||||
let RootAV4 = () => {
|
||||
return 'A4';
|
||||
};
|
||||
$RefreshReg$(RootAV4, 'RootA');
|
||||
ReactFreshRuntime.performReactRefresh();
|
||||
expect(containerA.innerHTML).toBe('A4');
|
||||
expect(containerB.innerHTML).toBe('B1');
|
||||
}
|
||||
});
|
||||
|
||||
it('remounts classes on every edit', () => {
|
||||
if (__DEV__) {
|
||||
let HelloV1 = render(() => {
|
||||
|
||||
Reference in New Issue
Block a user