mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
(Temporarily) revert unmounting error boundaries changes (#20147)
This reverts commitsbcca5a6ca7andffb749c95e, although neither revert cleanly since methods have been moved between the work-loop and commit-work files. This commit is a mostly manual effort of undoing the changes.
This commit is contained in:
@@ -2473,175 +2473,4 @@ describe('ReactErrorBoundaries', () => {
|
||||
'Caught an error: gotta catch em all.',
|
||||
);
|
||||
});
|
||||
|
||||
// @gate skipUnmountedBoundaries
|
||||
it('catches errors thrown in componentWillUnmount', () => {
|
||||
class LocalErrorBoundary extends React.Component {
|
||||
state = {error: null};
|
||||
static getDerivedStateFromError(error) {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`ErrorBoundary static getDerivedStateFromError`,
|
||||
);
|
||||
return {error};
|
||||
}
|
||||
render() {
|
||||
const {children, id, fallbackID} = this.props;
|
||||
const {error} = this.state;
|
||||
if (error) {
|
||||
Scheduler.unstable_yieldValue(`${id} render error`);
|
||||
return <Component id={fallbackID} />;
|
||||
}
|
||||
Scheduler.unstable_yieldValue(`${id} render success`);
|
||||
return children || null;
|
||||
}
|
||||
}
|
||||
|
||||
class Component extends React.Component {
|
||||
render() {
|
||||
const {id} = this.props;
|
||||
Scheduler.unstable_yieldValue('Component render ' + id);
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
class LocalBrokenComponentWillUnmount extends React.Component {
|
||||
componentWillUnmount() {
|
||||
Scheduler.unstable_yieldValue(
|
||||
'BrokenComponentWillUnmount componentWillUnmount',
|
||||
);
|
||||
throw Error('Expected');
|
||||
}
|
||||
|
||||
render() {
|
||||
Scheduler.unstable_yieldValue('BrokenComponentWillUnmount render');
|
||||
return 'broken';
|
||||
}
|
||||
}
|
||||
|
||||
const container = document.createElement('div');
|
||||
|
||||
ReactDOM.render(
|
||||
<LocalErrorBoundary id="OuterBoundary" fallbackID="OuterFallback">
|
||||
<Component id="sibling" />
|
||||
<LocalErrorBoundary id="InnerBoundary" fallbackID="InnerFallback">
|
||||
<LocalBrokenComponentWillUnmount />
|
||||
</LocalErrorBoundary>
|
||||
</LocalErrorBoundary>,
|
||||
container,
|
||||
);
|
||||
|
||||
expect(container.firstChild.textContent).toBe('sibling');
|
||||
expect(container.lastChild.textContent).toBe('broken');
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'OuterBoundary render success',
|
||||
'Component render sibling',
|
||||
'InnerBoundary render success',
|
||||
'BrokenComponentWillUnmount render',
|
||||
]);
|
||||
|
||||
ReactDOM.render(
|
||||
<LocalErrorBoundary id="OuterBoundary" fallbackID="OuterFallback">
|
||||
<Component id="sibling" />
|
||||
</LocalErrorBoundary>,
|
||||
container,
|
||||
);
|
||||
|
||||
// React should skip over the unmounting boundary and find the nearest still-mounted boundary.
|
||||
expect(container.firstChild.textContent).toBe('OuterFallback');
|
||||
expect(container.lastChild.textContent).toBe('OuterFallback');
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'OuterBoundary render success',
|
||||
'Component render sibling',
|
||||
'BrokenComponentWillUnmount componentWillUnmount',
|
||||
'ErrorBoundary static getDerivedStateFromError',
|
||||
'OuterBoundary render error',
|
||||
'Component render OuterFallback',
|
||||
]);
|
||||
});
|
||||
|
||||
// @gate skipUnmountedBoundaries
|
||||
it('catches errors thrown while detaching refs', () => {
|
||||
class LocalErrorBoundary extends React.Component {
|
||||
state = {error: null};
|
||||
static getDerivedStateFromError(error) {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`ErrorBoundary static getDerivedStateFromError`,
|
||||
);
|
||||
return {error};
|
||||
}
|
||||
render() {
|
||||
const {children, id, fallbackID} = this.props;
|
||||
const {error} = this.state;
|
||||
if (error) {
|
||||
Scheduler.unstable_yieldValue(`${id} render error`);
|
||||
return <Component id={fallbackID} />;
|
||||
}
|
||||
Scheduler.unstable_yieldValue(`${id} render success`);
|
||||
return children || null;
|
||||
}
|
||||
}
|
||||
|
||||
class Component extends React.Component {
|
||||
render() {
|
||||
const {id} = this.props;
|
||||
Scheduler.unstable_yieldValue('Component render ' + id);
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
class LocalBrokenCallbackRef extends React.Component {
|
||||
_ref = ref => {
|
||||
Scheduler.unstable_yieldValue('LocalBrokenCallbackRef ref ' + !!ref);
|
||||
if (ref === null) {
|
||||
throw Error('Expected');
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
Scheduler.unstable_yieldValue('LocalBrokenCallbackRef render');
|
||||
return <div ref={this._ref}>ref</div>;
|
||||
}
|
||||
}
|
||||
|
||||
const container = document.createElement('div');
|
||||
|
||||
ReactDOM.render(
|
||||
<LocalErrorBoundary id="OuterBoundary" fallbackID="OuterFallback">
|
||||
<Component id="sibling" />
|
||||
<LocalErrorBoundary id="InnerBoundary" fallbackID="InnerFallback">
|
||||
<LocalBrokenCallbackRef />
|
||||
</LocalErrorBoundary>
|
||||
</LocalErrorBoundary>,
|
||||
container,
|
||||
);
|
||||
|
||||
expect(container.firstChild.textContent).toBe('sibling');
|
||||
expect(container.lastChild.textContent).toBe('ref');
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'OuterBoundary render success',
|
||||
'Component render sibling',
|
||||
'InnerBoundary render success',
|
||||
'LocalBrokenCallbackRef render',
|
||||
'LocalBrokenCallbackRef ref true',
|
||||
]);
|
||||
|
||||
ReactDOM.render(
|
||||
<LocalErrorBoundary id="OuterBoundary" fallbackID="OuterFallback">
|
||||
<Component id="sibling" />
|
||||
</LocalErrorBoundary>,
|
||||
container,
|
||||
);
|
||||
|
||||
// React should skip over the unmounting boundary and find the nearest still-mounted boundary.
|
||||
expect(container.firstChild.textContent).toBe('OuterFallback');
|
||||
expect(container.lastChild.textContent).toBe('OuterFallback');
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'OuterBoundary render success',
|
||||
'Component render sibling',
|
||||
'LocalBrokenCallbackRef ref false',
|
||||
'ErrorBoundary static getDerivedStateFromError',
|
||||
'OuterBoundary render error',
|
||||
'Component render OuterFallback',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1669,28 +1669,16 @@ describe('DOMPluginEventSystem', () => {
|
||||
|
||||
function Test() {
|
||||
React.useEffect(() => {
|
||||
const clearClick1 = setClick1(
|
||||
buttonRef.current,
|
||||
targetListener1,
|
||||
);
|
||||
const clearClick2 = setClick2(
|
||||
buttonRef.current,
|
||||
targetListener2,
|
||||
);
|
||||
const clearClick3 = setClick3(
|
||||
buttonRef.current,
|
||||
targetListener3,
|
||||
);
|
||||
const clearClick4 = setClick4(
|
||||
buttonRef.current,
|
||||
targetListener4,
|
||||
);
|
||||
setClick1(buttonRef.current, targetListener1);
|
||||
setClick2(buttonRef.current, targetListener2);
|
||||
setClick3(buttonRef.current, targetListener3);
|
||||
setClick4(buttonRef.current, targetListener4);
|
||||
|
||||
return () => {
|
||||
clearClick1();
|
||||
clearClick2();
|
||||
clearClick3();
|
||||
clearClick4();
|
||||
setClick1();
|
||||
setClick2();
|
||||
setClick3();
|
||||
setClick4();
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1715,28 +1703,16 @@ describe('DOMPluginEventSystem', () => {
|
||||
|
||||
function Test2() {
|
||||
React.useEffect(() => {
|
||||
const clearClick1 = setClick1(
|
||||
buttonRef.current,
|
||||
targetListener1,
|
||||
);
|
||||
const clearClick2 = setClick2(
|
||||
buttonRef.current,
|
||||
targetListener2,
|
||||
);
|
||||
const clearClick3 = setClick3(
|
||||
buttonRef.current,
|
||||
targetListener3,
|
||||
);
|
||||
const clearClick4 = setClick4(
|
||||
buttonRef.current,
|
||||
targetListener4,
|
||||
);
|
||||
setClick1(buttonRef.current, targetListener1);
|
||||
setClick2(buttonRef.current, targetListener2);
|
||||
setClick3(buttonRef.current, targetListener3);
|
||||
setClick4(buttonRef.current, targetListener4);
|
||||
|
||||
return () => {
|
||||
clearClick1();
|
||||
clearClick2();
|
||||
clearClick3();
|
||||
clearClick4();
|
||||
setClick1();
|
||||
setClick2();
|
||||
setClick3();
|
||||
setClick4();
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -185,11 +185,7 @@ const callComponentWillUnmountWithTimer = function(current, instance) {
|
||||
};
|
||||
|
||||
// Capture errors so they don't interrupt unmounting.
|
||||
function safelyCallComponentWillUnmount(
|
||||
current: Fiber,
|
||||
instance: any,
|
||||
nearestMountedAncestor: Fiber | null,
|
||||
) {
|
||||
function safelyCallComponentWillUnmount(current, instance) {
|
||||
if (__DEV__) {
|
||||
invokeGuardedCallback(
|
||||
null,
|
||||
@@ -200,19 +196,19 @@ function safelyCallComponentWillUnmount(
|
||||
);
|
||||
if (hasCaughtError()) {
|
||||
const unmountError = clearCaughtError();
|
||||
captureCommitPhaseError(current, nearestMountedAncestor, unmountError);
|
||||
captureCommitPhaseError(current, unmountError);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
callComponentWillUnmountWithTimer(current, instance);
|
||||
} catch (unmountError) {
|
||||
captureCommitPhaseError(current, nearestMountedAncestor, unmountError);
|
||||
captureCommitPhaseError(current, unmountError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @noinline */
|
||||
function safelyDetachRef(current: Fiber, nearestMountedAncestor: Fiber) {
|
||||
function safelyDetachRef(current: Fiber) {
|
||||
const ref = current.ref;
|
||||
if (ref !== null) {
|
||||
if (typeof ref === 'function') {
|
||||
@@ -231,7 +227,7 @@ function safelyDetachRef(current: Fiber, nearestMountedAncestor: Fiber) {
|
||||
|
||||
if (hasCaughtError()) {
|
||||
const refError = clearCaughtError();
|
||||
captureCommitPhaseError(current, nearestMountedAncestor, refError);
|
||||
captureCommitPhaseError(current, refError);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
@@ -250,7 +246,7 @@ function safelyDetachRef(current: Fiber, nearestMountedAncestor: Fiber) {
|
||||
ref(null);
|
||||
}
|
||||
} catch (refError) {
|
||||
captureCommitPhaseError(current, nearestMountedAncestor, refError);
|
||||
captureCommitPhaseError(current, refError);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -259,32 +255,24 @@ function safelyDetachRef(current: Fiber, nearestMountedAncestor: Fiber) {
|
||||
}
|
||||
}
|
||||
|
||||
export function safelyCallDestroy(
|
||||
current: Fiber,
|
||||
nearestMountedAncestor: Fiber | null,
|
||||
destroy: () => void,
|
||||
) {
|
||||
export function safelyCallDestroy(current: Fiber, destroy: () => void) {
|
||||
if (__DEV__) {
|
||||
invokeGuardedCallback(null, destroy, null);
|
||||
if (hasCaughtError()) {
|
||||
const error = clearCaughtError();
|
||||
captureCommitPhaseError(current, nearestMountedAncestor, error);
|
||||
captureCommitPhaseError(current, error);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
destroy();
|
||||
} catch (error) {
|
||||
captureCommitPhaseError(current, nearestMountedAncestor, error);
|
||||
captureCommitPhaseError(current, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @noinline */
|
||||
function commitHookEffectListUnmount(
|
||||
flags: HookFlags,
|
||||
finishedWork: Fiber,
|
||||
nearestMountedAncestor: Fiber | null,
|
||||
) {
|
||||
function commitHookEffectListUnmount(flags: HookFlags, finishedWork: Fiber) {
|
||||
const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
|
||||
const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
|
||||
if (lastEffect !== null) {
|
||||
@@ -296,7 +284,7 @@ function commitHookEffectListUnmount(
|
||||
const destroy = effect.destroy;
|
||||
effect.destroy = undefined;
|
||||
if (destroy !== undefined) {
|
||||
safelyCallDestroy(finishedWork, nearestMountedAncestor, destroy);
|
||||
safelyCallDestroy(finishedWork, destroy);
|
||||
}
|
||||
}
|
||||
effect = effect.next;
|
||||
@@ -442,14 +430,14 @@ function recursivelyCommitBeforeMutationEffects(firstChild: Fiber) {
|
||||
);
|
||||
if (hasCaughtError()) {
|
||||
const error = clearCaughtError();
|
||||
captureCommitPhaseError(fiber, fiber.return, error);
|
||||
captureCommitPhaseError(fiber, error);
|
||||
}
|
||||
resetCurrentDebugFiberInDEV();
|
||||
} else {
|
||||
try {
|
||||
commitBeforeMutationEffectsOnFiber(fiber);
|
||||
} catch (error) {
|
||||
captureCommitPhaseError(fiber, fiber.return, error);
|
||||
captureCommitPhaseError(fiber, error);
|
||||
}
|
||||
}
|
||||
fiber = fiber.sibling;
|
||||
@@ -492,14 +480,14 @@ function iterativelyCommitBeforeMutationEffects_complete() {
|
||||
);
|
||||
if (hasCaughtError()) {
|
||||
const error = clearCaughtError();
|
||||
captureCommitPhaseError(fiber, fiber.return, error);
|
||||
captureCommitPhaseError(fiber, error);
|
||||
}
|
||||
resetCurrentDebugFiberInDEV();
|
||||
} else {
|
||||
try {
|
||||
commitBeforeMutationEffectsOnFiber(fiber);
|
||||
} catch (error) {
|
||||
captureCommitPhaseError(fiber, fiber.return, error);
|
||||
captureCommitPhaseError(fiber, error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -668,12 +656,7 @@ function recursivelyCommitMutationEffects(
|
||||
while (fiber !== null) {
|
||||
const deletions = fiber.deletions;
|
||||
if (deletions !== null) {
|
||||
commitMutationEffectsDeletions(
|
||||
deletions,
|
||||
fiber,
|
||||
root,
|
||||
renderPriorityLevel,
|
||||
);
|
||||
commitMutationEffectsDeletions(deletions, root, renderPriorityLevel);
|
||||
}
|
||||
|
||||
if (fiber.child !== null) {
|
||||
@@ -699,14 +682,14 @@ function recursivelyCommitMutationEffects(
|
||||
);
|
||||
if (hasCaughtError()) {
|
||||
const error = clearCaughtError();
|
||||
captureCommitPhaseError(fiber, fiber.return, error);
|
||||
captureCommitPhaseError(fiber, error);
|
||||
}
|
||||
resetCurrentDebugFiberInDEV();
|
||||
} else {
|
||||
try {
|
||||
commitMutationEffectsOnFiber(fiber, root, renderPriorityLevel);
|
||||
} catch (error) {
|
||||
captureCommitPhaseError(fiber, fiber.return, error);
|
||||
captureCommitPhaseError(fiber, error);
|
||||
}
|
||||
}
|
||||
fiber = fiber.sibling;
|
||||
@@ -723,12 +706,7 @@ function iterativelyCommitMutationEffects_begin(
|
||||
// TODO: Should wrap this in flags check, too, as optimization
|
||||
const deletions = fiber.deletions;
|
||||
if (deletions !== null) {
|
||||
commitMutationEffectsDeletions(
|
||||
deletions,
|
||||
fiber,
|
||||
root,
|
||||
renderPriorityLevel,
|
||||
);
|
||||
commitMutationEffectsDeletions(deletions, root, renderPriorityLevel);
|
||||
}
|
||||
|
||||
const child = fiber.child;
|
||||
@@ -759,14 +737,14 @@ function iterativelyCommitMutationEffects_complete(
|
||||
);
|
||||
if (hasCaughtError()) {
|
||||
const error = clearCaughtError();
|
||||
captureCommitPhaseError(fiber, fiber.return, error);
|
||||
captureCommitPhaseError(fiber, error);
|
||||
}
|
||||
resetCurrentDebugFiberInDEV();
|
||||
} else {
|
||||
try {
|
||||
commitMutationEffectsOnFiber(fiber, root, renderPriorityLevel);
|
||||
} catch (error) {
|
||||
captureCommitPhaseError(fiber, fiber.return, error);
|
||||
captureCommitPhaseError(fiber, error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -855,7 +833,6 @@ function commitMutationEffectsOnFiber(
|
||||
/** @noinline */
|
||||
function commitMutationEffectsDeletions(
|
||||
deletions: Array<Fiber>,
|
||||
nearestMountedAncestor: Fiber,
|
||||
root: FiberRoot,
|
||||
renderPriorityLevel,
|
||||
) {
|
||||
@@ -868,23 +845,17 @@ function commitMutationEffectsDeletions(
|
||||
null,
|
||||
root,
|
||||
childToDelete,
|
||||
nearestMountedAncestor,
|
||||
renderPriorityLevel,
|
||||
);
|
||||
if (hasCaughtError()) {
|
||||
const error = clearCaughtError();
|
||||
captureCommitPhaseError(childToDelete, nearestMountedAncestor, error);
|
||||
captureCommitPhaseError(childToDelete, error);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
commitDeletion(
|
||||
root,
|
||||
childToDelete,
|
||||
nearestMountedAncestor,
|
||||
renderPriorityLevel,
|
||||
);
|
||||
commitDeletion(root, childToDelete, renderPriorityLevel);
|
||||
} catch (error) {
|
||||
captureCommitPhaseError(childToDelete, nearestMountedAncestor, error);
|
||||
captureCommitPhaseError(childToDelete, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -906,14 +877,14 @@ export function commitLayoutEffects(
|
||||
);
|
||||
if (hasCaughtError()) {
|
||||
const error = clearCaughtError();
|
||||
captureCommitPhaseError(finishedWork, null, error);
|
||||
captureCommitPhaseError(finishedWork, error);
|
||||
}
|
||||
resetCurrentDebugFiberInDEV();
|
||||
} else {
|
||||
try {
|
||||
recursivelyCommitLayoutEffects(finishedWork, finishedRoot);
|
||||
} catch (error) {
|
||||
captureCommitPhaseError(finishedWork, null, error);
|
||||
captureCommitPhaseError(finishedWork, error);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -951,7 +922,7 @@ function recursivelyCommitLayoutEffects(
|
||||
);
|
||||
if (hasCaughtError()) {
|
||||
const error = clearCaughtError();
|
||||
captureCommitPhaseError(child, finishedWork, error);
|
||||
captureCommitPhaseError(child, error);
|
||||
}
|
||||
if (prevCurrentFiberInDEV !== null) {
|
||||
setCurrentDebugFiberInDEV(prevCurrentFiberInDEV);
|
||||
@@ -962,7 +933,7 @@ function recursivelyCommitLayoutEffects(
|
||||
try {
|
||||
recursivelyCommitLayoutEffects(child, finishedRoot);
|
||||
} catch (error) {
|
||||
captureCommitPhaseError(child, finishedWork, error);
|
||||
captureCommitPhaseError(child, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -984,7 +955,7 @@ function recursivelyCommitLayoutEffects(
|
||||
);
|
||||
if (hasCaughtError()) {
|
||||
const error = clearCaughtError();
|
||||
captureCommitPhaseError(finishedWork, finishedWork.return, error);
|
||||
captureCommitPhaseError(finishedWork, error);
|
||||
}
|
||||
if (prevCurrentFiberInDEV !== null) {
|
||||
setCurrentDebugFiberInDEV(prevCurrentFiberInDEV);
|
||||
@@ -995,7 +966,7 @@ function recursivelyCommitLayoutEffects(
|
||||
try {
|
||||
commitLayoutEffectsForProfiler(finishedWork, finishedRoot);
|
||||
} catch (error) {
|
||||
captureCommitPhaseError(finishedWork, finishedWork.return, error);
|
||||
captureCommitPhaseError(finishedWork, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1036,7 +1007,7 @@ function recursivelyCommitLayoutEffects(
|
||||
);
|
||||
if (hasCaughtError()) {
|
||||
const error = clearCaughtError();
|
||||
captureCommitPhaseError(child, finishedWork, error);
|
||||
captureCommitPhaseError(child, error);
|
||||
}
|
||||
if (prevCurrentFiberInDEV !== null) {
|
||||
setCurrentDebugFiberInDEV(prevCurrentFiberInDEV);
|
||||
@@ -1047,7 +1018,7 @@ function recursivelyCommitLayoutEffects(
|
||||
try {
|
||||
recursivelyCommitLayoutEffects(child, finishedRoot);
|
||||
} catch (error) {
|
||||
captureCommitPhaseError(child, finishedWork, error);
|
||||
captureCommitPhaseError(child, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1175,14 +1146,14 @@ function iterativelyCommitLayoutEffects_begin(
|
||||
);
|
||||
if (hasCaughtError()) {
|
||||
const error = clearCaughtError();
|
||||
captureCommitPhaseError(finishedWork, finishedWork.return, error);
|
||||
captureCommitPhaseError(finishedWork, error);
|
||||
}
|
||||
resetCurrentDebugFiberInDEV();
|
||||
} else {
|
||||
try {
|
||||
commitLayoutEffectsForProfiler(finishedWork, finishedRoot);
|
||||
} catch (error) {
|
||||
captureCommitPhaseError(finishedWork, finishedWork.return, error);
|
||||
captureCommitPhaseError(finishedWork, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1236,14 +1207,14 @@ function iterativelyCommitLayoutEffects_complete(
|
||||
);
|
||||
if (hasCaughtError()) {
|
||||
const error = clearCaughtError();
|
||||
captureCommitPhaseError(fiber, fiber.return, error);
|
||||
captureCommitPhaseError(fiber, error);
|
||||
}
|
||||
resetCurrentDebugFiberInDEV();
|
||||
} else {
|
||||
try {
|
||||
commitLayoutEffectsOnFiber(finishedRoot, fiber);
|
||||
} catch (error) {
|
||||
captureCommitPhaseError(fiber, fiber.return, error);
|
||||
captureCommitPhaseError(fiber, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1690,14 +1661,14 @@ function recursivelyCommitPassiveMountEffects(
|
||||
);
|
||||
if (hasCaughtError()) {
|
||||
const error = clearCaughtError();
|
||||
captureCommitPhaseError(fiber, fiber.return, error);
|
||||
captureCommitPhaseError(fiber, error);
|
||||
}
|
||||
resetCurrentDebugFiberInDEV();
|
||||
} else {
|
||||
try {
|
||||
commitPassiveMountOnFiber(root, fiber);
|
||||
} catch (error) {
|
||||
captureCommitPhaseError(fiber, fiber.return, error);
|
||||
captureCommitPhaseError(fiber, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1755,14 +1726,14 @@ function iterativelyCommitPassiveMountEffects_begin(
|
||||
);
|
||||
if (hasCaughtError()) {
|
||||
const error = clearCaughtError();
|
||||
captureCommitPhaseError(fiber, fiber.return, error);
|
||||
captureCommitPhaseError(fiber, error);
|
||||
}
|
||||
resetCurrentDebugFiberInDEV();
|
||||
} else {
|
||||
try {
|
||||
commitProfilerPassiveEffect(root, fiber);
|
||||
} catch (error) {
|
||||
captureCommitPhaseError(fiber, fiber.return, error);
|
||||
captureCommitPhaseError(fiber, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1816,14 +1787,14 @@ function iterativelyCommitPassiveMountEffects_complete(
|
||||
);
|
||||
if (hasCaughtError()) {
|
||||
const error = clearCaughtError();
|
||||
captureCommitPhaseError(fiber, fiber.return, error);
|
||||
captureCommitPhaseError(fiber, error);
|
||||
}
|
||||
resetCurrentDebugFiberInDEV();
|
||||
} else {
|
||||
try {
|
||||
commitPassiveMountOnFiber(root, fiber);
|
||||
} catch (error) {
|
||||
captureCommitPhaseError(fiber, fiber.return, error);
|
||||
captureCommitPhaseError(fiber, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1862,7 +1833,6 @@ function recursivelyCommitPassiveUnmountEffects(firstChild: Fiber): void {
|
||||
const fiberToDelete = deletions[i];
|
||||
recursivelyCommitPassiveUnmountEffectsInsideOfDeletedTree(
|
||||
fiberToDelete,
|
||||
fiber,
|
||||
);
|
||||
|
||||
// Now that passive effects have been processed, it's safe to detach lingering pointers.
|
||||
@@ -1906,7 +1876,6 @@ function iterativelyCommitPassiveUnmountEffects_begin() {
|
||||
nextEffect = fiberToDelete;
|
||||
iterativelyCommitPassiveUnmountEffectsInsideOfDeletedTree_begin(
|
||||
fiberToDelete,
|
||||
fiber,
|
||||
);
|
||||
|
||||
// Now that passive effects have been processed, it's safe to detach lingering pointers.
|
||||
@@ -1946,7 +1915,6 @@ function iterativelyCommitPassiveUnmountEffects_complete() {
|
||||
|
||||
function recursivelyCommitPassiveUnmountEffectsInsideOfDeletedTree(
|
||||
fiberToDelete: Fiber,
|
||||
nearestMountedAncestor: Fiber,
|
||||
): void {
|
||||
if ((fiberToDelete.subtreeFlags & PassiveStatic) !== NoFlags) {
|
||||
// If any children have passive effects then traverse the subtree.
|
||||
@@ -1955,27 +1923,20 @@ function recursivelyCommitPassiveUnmountEffectsInsideOfDeletedTree(
|
||||
// since that would not cover passive effects in siblings.
|
||||
let child = fiberToDelete.child;
|
||||
while (child !== null) {
|
||||
recursivelyCommitPassiveUnmountEffectsInsideOfDeletedTree(
|
||||
child,
|
||||
nearestMountedAncestor,
|
||||
);
|
||||
recursivelyCommitPassiveUnmountEffectsInsideOfDeletedTree(child);
|
||||
child = child.sibling;
|
||||
}
|
||||
}
|
||||
|
||||
if ((fiberToDelete.flags & PassiveStatic) !== NoFlags) {
|
||||
setCurrentDebugFiberInDEV(fiberToDelete);
|
||||
commitPassiveUnmountInsideDeletedTreeOnFiber(
|
||||
fiberToDelete,
|
||||
nearestMountedAncestor,
|
||||
);
|
||||
commitPassiveUnmountInsideDeletedTreeOnFiber(fiberToDelete);
|
||||
resetCurrentDebugFiberInDEV();
|
||||
}
|
||||
}
|
||||
|
||||
function iterativelyCommitPassiveUnmountEffectsInsideOfDeletedTree_begin(
|
||||
deletedSubtreeRoot: Fiber,
|
||||
nearestMountedAncestor: Fiber,
|
||||
) {
|
||||
while (nextEffect !== null) {
|
||||
const fiber = nextEffect;
|
||||
@@ -1986,7 +1947,6 @@ function iterativelyCommitPassiveUnmountEffectsInsideOfDeletedTree_begin(
|
||||
} else {
|
||||
iterativelyCommitPassiveUnmountEffectsInsideOfDeletedTree_complete(
|
||||
deletedSubtreeRoot,
|
||||
nearestMountedAncestor,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1994,16 +1954,12 @@ function iterativelyCommitPassiveUnmountEffectsInsideOfDeletedTree_begin(
|
||||
|
||||
function iterativelyCommitPassiveUnmountEffectsInsideOfDeletedTree_complete(
|
||||
deletedSubtreeRoot: Fiber,
|
||||
nearestMountedAncestor: Fiber,
|
||||
) {
|
||||
while (nextEffect !== null) {
|
||||
const fiber = nextEffect;
|
||||
if ((fiber.flags & PassiveStatic) !== NoFlags) {
|
||||
setCurrentDebugFiberInDEV(fiber);
|
||||
commitPassiveUnmountInsideDeletedTreeOnFiber(
|
||||
fiber,
|
||||
nearestMountedAncestor,
|
||||
);
|
||||
commitPassiveUnmountInsideDeletedTreeOnFiber(fiber);
|
||||
resetCurrentDebugFiberInDEV();
|
||||
}
|
||||
|
||||
@@ -2118,7 +2074,6 @@ function commitDetachRef(current: Fiber) {
|
||||
function commitUnmount(
|
||||
finishedRoot: FiberRoot,
|
||||
current: Fiber,
|
||||
nearestMountedAncestor: Fiber,
|
||||
renderPriorityLevel: ReactPriorityLevel,
|
||||
): void {
|
||||
onCommitUnmount(current);
|
||||
@@ -2145,10 +2100,10 @@ function commitUnmount(
|
||||
current.mode & ProfileMode
|
||||
) {
|
||||
startLayoutEffectTimer();
|
||||
safelyCallDestroy(current, nearestMountedAncestor, destroy);
|
||||
safelyCallDestroy(current, destroy);
|
||||
recordLayoutEffectDuration(current);
|
||||
} else {
|
||||
safelyCallDestroy(current, nearestMountedAncestor, destroy);
|
||||
safelyCallDestroy(current, destroy);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2159,19 +2114,15 @@ function commitUnmount(
|
||||
return;
|
||||
}
|
||||
case ClassComponent: {
|
||||
safelyDetachRef(current, nearestMountedAncestor);
|
||||
safelyDetachRef(current);
|
||||
const instance = current.stateNode;
|
||||
if (typeof instance.componentWillUnmount === 'function') {
|
||||
safelyCallComponentWillUnmount(
|
||||
current,
|
||||
instance,
|
||||
nearestMountedAncestor,
|
||||
);
|
||||
safelyCallComponentWillUnmount(current, instance);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case HostComponent: {
|
||||
safelyDetachRef(current, nearestMountedAncestor);
|
||||
safelyDetachRef(current);
|
||||
return;
|
||||
}
|
||||
case HostPortal: {
|
||||
@@ -2179,12 +2130,7 @@ function commitUnmount(
|
||||
// We are also not using this parent because
|
||||
// the portal will get pushed immediately.
|
||||
if (supportsMutation) {
|
||||
unmountHostComponents(
|
||||
finishedRoot,
|
||||
current,
|
||||
nearestMountedAncestor,
|
||||
renderPriorityLevel,
|
||||
);
|
||||
unmountHostComponents(finishedRoot, current, renderPriorityLevel);
|
||||
} else if (supportsPersistence) {
|
||||
emptyPortalContainer(current);
|
||||
}
|
||||
@@ -2214,7 +2160,7 @@ function commitUnmount(
|
||||
}
|
||||
case ScopeComponent: {
|
||||
if (enableScopeAPI) {
|
||||
safelyDetachRef(current, nearestMountedAncestor);
|
||||
safelyDetachRef(current);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -2224,7 +2170,6 @@ function commitUnmount(
|
||||
function commitNestedUnmounts(
|
||||
finishedRoot: FiberRoot,
|
||||
root: Fiber,
|
||||
nearestMountedAncestor: Fiber,
|
||||
renderPriorityLevel: ReactPriorityLevel,
|
||||
): void {
|
||||
// While we're inside a removed host node we don't want to call
|
||||
@@ -2234,12 +2179,7 @@ function commitNestedUnmounts(
|
||||
// we do an inner loop while we're still inside the host node.
|
||||
let node: Fiber = root;
|
||||
while (true) {
|
||||
commitUnmount(
|
||||
finishedRoot,
|
||||
node,
|
||||
nearestMountedAncestor,
|
||||
renderPriorityLevel,
|
||||
);
|
||||
commitUnmount(finishedRoot, node, renderPriorityLevel);
|
||||
// Visit children because they may contain more composite or host nodes.
|
||||
// Skip portals because commitUnmount() currently visits them recursively.
|
||||
if (
|
||||
@@ -2520,10 +2460,9 @@ function insertOrAppendPlacementNode(
|
||||
}
|
||||
|
||||
function unmountHostComponents(
|
||||
finishedRoot: FiberRoot,
|
||||
current: Fiber,
|
||||
nearestMountedAncestor: Fiber,
|
||||
renderPriorityLevel: ReactPriorityLevel,
|
||||
finishedRoot,
|
||||
current,
|
||||
renderPriorityLevel,
|
||||
): void {
|
||||
// We only have the top Fiber that was deleted but we need to recurse down its
|
||||
// children to find all the terminal nodes.
|
||||
@@ -2572,12 +2511,7 @@ function unmountHostComponents(
|
||||
}
|
||||
|
||||
if (node.tag === HostComponent || node.tag === HostText) {
|
||||
commitNestedUnmounts(
|
||||
finishedRoot,
|
||||
node,
|
||||
nearestMountedAncestor,
|
||||
renderPriorityLevel,
|
||||
);
|
||||
commitNestedUnmounts(finishedRoot, node, renderPriorityLevel);
|
||||
// After all the children have unmounted, it is now safe to remove the
|
||||
// node from the tree.
|
||||
if (currentParentIsContainer) {
|
||||
@@ -2594,12 +2528,7 @@ function unmountHostComponents(
|
||||
// Don't visit children because we already visited them.
|
||||
} else if (enableFundamentalAPI && node.tag === FundamentalComponent) {
|
||||
const fundamentalNode = node.stateNode.instance;
|
||||
commitNestedUnmounts(
|
||||
finishedRoot,
|
||||
node,
|
||||
nearestMountedAncestor,
|
||||
renderPriorityLevel,
|
||||
);
|
||||
commitNestedUnmounts(finishedRoot, node, renderPriorityLevel);
|
||||
// After all the children have unmounted, it is now safe to remove the
|
||||
// node from the tree.
|
||||
if (currentParentIsContainer) {
|
||||
@@ -2651,12 +2580,7 @@ function unmountHostComponents(
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
commitUnmount(
|
||||
finishedRoot,
|
||||
node,
|
||||
nearestMountedAncestor,
|
||||
renderPriorityLevel,
|
||||
);
|
||||
commitUnmount(finishedRoot, node, renderPriorityLevel);
|
||||
// Visit children because we may find more host components below.
|
||||
if (node.child !== null) {
|
||||
node.child.return = node;
|
||||
@@ -2686,26 +2610,15 @@ function unmountHostComponents(
|
||||
function commitDeletion(
|
||||
finishedRoot: FiberRoot,
|
||||
current: Fiber,
|
||||
nearestMountedAncestor: Fiber,
|
||||
renderPriorityLevel: ReactPriorityLevel,
|
||||
): void {
|
||||
if (supportsMutation) {
|
||||
// Recursively delete all host nodes from the parent.
|
||||
// Detach refs and call componentWillUnmount() on the whole subtree.
|
||||
unmountHostComponents(
|
||||
finishedRoot,
|
||||
current,
|
||||
nearestMountedAncestor,
|
||||
renderPriorityLevel,
|
||||
);
|
||||
unmountHostComponents(finishedRoot, current, renderPriorityLevel);
|
||||
} else {
|
||||
// Detach refs and call componentWillUnmount() on the whole subtree.
|
||||
commitNestedUnmounts(
|
||||
finishedRoot,
|
||||
current,
|
||||
nearestMountedAncestor,
|
||||
renderPriorityLevel,
|
||||
);
|
||||
commitNestedUnmounts(finishedRoot, current, renderPriorityLevel);
|
||||
}
|
||||
const alternate = current.alternate;
|
||||
detachFiberMutation(current);
|
||||
@@ -2736,17 +2649,12 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
|
||||
commitHookEffectListUnmount(
|
||||
HookLayout | HookHasEffect,
|
||||
finishedWork,
|
||||
finishedWork.return,
|
||||
);
|
||||
} finally {
|
||||
recordLayoutEffectDuration(finishedWork);
|
||||
}
|
||||
} else {
|
||||
commitHookEffectListUnmount(
|
||||
HookLayout | HookHasEffect,
|
||||
finishedWork,
|
||||
finishedWork.return,
|
||||
);
|
||||
commitHookEffectListUnmount(HookLayout | HookHasEffect, finishedWork);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -2800,20 +2708,12 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
|
||||
) {
|
||||
try {
|
||||
startLayoutEffectTimer();
|
||||
commitHookEffectListUnmount(
|
||||
HookLayout | HookHasEffect,
|
||||
finishedWork,
|
||||
finishedWork.return,
|
||||
);
|
||||
commitHookEffectListUnmount(HookLayout | HookHasEffect, finishedWork);
|
||||
} finally {
|
||||
recordLayoutEffectDuration(finishedWork);
|
||||
}
|
||||
} else {
|
||||
commitHookEffectListUnmount(
|
||||
HookLayout | HookHasEffect,
|
||||
finishedWork,
|
||||
finishedWork.return,
|
||||
);
|
||||
commitHookEffectListUnmount(HookLayout | HookHasEffect, finishedWork);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -3049,28 +2949,17 @@ function commitPassiveUnmountOnFiber(finishedWork: Fiber): void {
|
||||
finishedWork.mode & ProfileMode
|
||||
) {
|
||||
startPassiveEffectTimer();
|
||||
commitHookEffectListUnmount(
|
||||
HookPassive | HookHasEffect,
|
||||
finishedWork,
|
||||
finishedWork.return,
|
||||
);
|
||||
commitHookEffectListUnmount(HookPassive | HookHasEffect, finishedWork);
|
||||
recordPassiveEffectDuration(finishedWork);
|
||||
} else {
|
||||
commitHookEffectListUnmount(
|
||||
HookPassive | HookHasEffect,
|
||||
finishedWork,
|
||||
finishedWork.return,
|
||||
);
|
||||
commitHookEffectListUnmount(HookPassive | HookHasEffect, finishedWork);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function commitPassiveUnmountInsideDeletedTreeOnFiber(
|
||||
current: Fiber,
|
||||
nearestMountedAncestor: Fiber | null,
|
||||
): void {
|
||||
function commitPassiveUnmountInsideDeletedTreeOnFiber(current: Fiber): void {
|
||||
switch (current.tag) {
|
||||
case FunctionComponent:
|
||||
case ForwardRef:
|
||||
@@ -3081,18 +2970,10 @@ function commitPassiveUnmountInsideDeletedTreeOnFiber(
|
||||
current.mode & ProfileMode
|
||||
) {
|
||||
startPassiveEffectTimer();
|
||||
commitHookEffectListUnmount(
|
||||
HookPassive,
|
||||
current,
|
||||
nearestMountedAncestor,
|
||||
);
|
||||
commitHookEffectListUnmount(HookPassive, current);
|
||||
recordPassiveEffectDuration(current);
|
||||
} else {
|
||||
commitHookEffectListUnmount(
|
||||
HookPassive,
|
||||
current,
|
||||
nearestMountedAncestor,
|
||||
);
|
||||
commitHookEffectListUnmount(HookPassive, current);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -3147,7 +3028,7 @@ function invokeLayoutEffectMountInDEV(fiber: Fiber): void {
|
||||
);
|
||||
if (hasCaughtError()) {
|
||||
const mountError = clearCaughtError();
|
||||
captureCommitPhaseError(fiber, fiber.return, mountError);
|
||||
captureCommitPhaseError(fiber, mountError);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -3156,7 +3037,7 @@ function invokeLayoutEffectMountInDEV(fiber: Fiber): void {
|
||||
invokeGuardedCallback(null, instance.componentDidMount, instance);
|
||||
if (hasCaughtError()) {
|
||||
const mountError = clearCaughtError();
|
||||
captureCommitPhaseError(fiber, fiber.return, mountError);
|
||||
captureCommitPhaseError(fiber, mountError);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -3181,7 +3062,7 @@ function invokePassiveEffectMountInDEV(fiber: Fiber): void {
|
||||
);
|
||||
if (hasCaughtError()) {
|
||||
const mountError = clearCaughtError();
|
||||
captureCommitPhaseError(fiber, fiber.return, mountError);
|
||||
captureCommitPhaseError(fiber, mountError);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -3203,18 +3084,17 @@ function invokeLayoutEffectUnmountInDEV(fiber: Fiber): void {
|
||||
null,
|
||||
HookLayout | HookHasEffect,
|
||||
fiber,
|
||||
fiber.return,
|
||||
);
|
||||
if (hasCaughtError()) {
|
||||
const unmountError = clearCaughtError();
|
||||
captureCommitPhaseError(fiber, fiber.return, unmountError);
|
||||
captureCommitPhaseError(fiber, unmountError);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ClassComponent: {
|
||||
const instance = fiber.stateNode;
|
||||
if (typeof instance.componentWillUnmount === 'function') {
|
||||
safelyCallComponentWillUnmount(fiber, instance, fiber.return);
|
||||
safelyCallComponentWillUnmount(fiber, instance);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -3236,11 +3116,10 @@ function invokePassiveEffectUnmountInDEV(fiber: Fiber): void {
|
||||
null,
|
||||
HookPassive | HookHasEffect,
|
||||
fiber,
|
||||
fiber.return,
|
||||
);
|
||||
if (hasCaughtError()) {
|
||||
const unmountError = clearCaughtError();
|
||||
captureCommitPhaseError(fiber, fiber.return, unmountError);
|
||||
captureCommitPhaseError(fiber, unmountError);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ import {
|
||||
decoupleUpdatePriorityFromScheduler,
|
||||
enableDebugTracing,
|
||||
enableSchedulingProfiler,
|
||||
skipUnmountedBoundaries,
|
||||
enableDoubleInvokingEffects,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import ReactSharedInternals from 'shared/ReactSharedInternals';
|
||||
@@ -2201,11 +2200,7 @@ function captureCommitPhaseErrorOnRoot(
|
||||
}
|
||||
}
|
||||
|
||||
export function captureCommitPhaseError(
|
||||
sourceFiber: Fiber,
|
||||
nearestMountedAncestor: Fiber | null,
|
||||
error: mixed,
|
||||
) {
|
||||
export function captureCommitPhaseError(sourceFiber: Fiber, error: mixed) {
|
||||
if (sourceFiber.tag === HostRoot) {
|
||||
// Error was thrown at the root. There is no parent, so the root
|
||||
// itself should capture it.
|
||||
@@ -2213,13 +2208,7 @@ export function captureCommitPhaseError(
|
||||
return;
|
||||
}
|
||||
|
||||
let fiber = null;
|
||||
if (skipUnmountedBoundaries) {
|
||||
fiber = nearestMountedAncestor;
|
||||
} else {
|
||||
fiber = sourceFiber.return;
|
||||
}
|
||||
|
||||
let fiber = sourceFiber.return;
|
||||
while (fiber !== null) {
|
||||
if (fiber.tag === HostRoot) {
|
||||
captureCommitPhaseErrorOnRoot(fiber, sourceFiber, error);
|
||||
|
||||
@@ -2298,10 +2298,7 @@ function commitBeforeMutationEffects() {
|
||||
}
|
||||
}
|
||||
|
||||
function commitMutationEffects(
|
||||
root: FiberRoot,
|
||||
renderPriorityLevel: ReactPriorityLevel,
|
||||
) {
|
||||
function commitMutationEffects(root: FiberRoot, renderPriorityLevel) {
|
||||
// TODO: Should probably move the bulk of this function to commitWork.
|
||||
while (nextEffect !== null) {
|
||||
setCurrentDebugFiberInDEV(nextEffect);
|
||||
@@ -2760,7 +2757,6 @@ export function captureCommitPhaseError(sourceFiber: Fiber, error: mixed) {
|
||||
}
|
||||
|
||||
let fiber = sourceFiber.return;
|
||||
|
||||
while (fiber !== null) {
|
||||
if (fiber.tag === HostRoot) {
|
||||
captureCommitPhaseErrorOnRoot(fiber, sourceFiber, error);
|
||||
@@ -2786,24 +2782,6 @@ export function captureCommitPhaseError(sourceFiber: Fiber, error: mixed) {
|
||||
markRootUpdated(root, SyncLane, eventTime);
|
||||
ensureRootIsScheduled(root, eventTime);
|
||||
schedulePendingInteractions(root, SyncLane);
|
||||
} else {
|
||||
// This component has already been unmounted.
|
||||
// We can't schedule any follow up work for the root because the fiber is already unmounted,
|
||||
// but we can still call the log-only boundary so the error isn't swallowed.
|
||||
//
|
||||
// TODO This is only a temporary bandaid for the old reconciler fork.
|
||||
// We can delete this special case once the new fork is merged.
|
||||
if (
|
||||
typeof instance.componentDidCatch === 'function' &&
|
||||
!isAlreadyFailedLegacyErrorBoundary(instance)
|
||||
) {
|
||||
try {
|
||||
instance.componentDidCatch(error, errorInfo);
|
||||
} catch (errorToIgnore) {
|
||||
// TODO Ignore this error? Rethrow it?
|
||||
// This is kind of an edge case.
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2353,388 +2353,6 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||
expect(ReactNoop.getChildren()).toEqual([]);
|
||||
});
|
||||
|
||||
describe('errors thrown in passive destroy function within unmounted trees', () => {
|
||||
let BrokenUseEffectCleanup;
|
||||
let ErrorBoundary;
|
||||
let DerivedStateOnlyErrorBoundary;
|
||||
let LogOnlyErrorBoundary;
|
||||
|
||||
beforeEach(() => {
|
||||
BrokenUseEffectCleanup = function() {
|
||||
useEffect(() => {
|
||||
Scheduler.unstable_yieldValue('BrokenUseEffectCleanup useEffect');
|
||||
return () => {
|
||||
Scheduler.unstable_yieldValue(
|
||||
'BrokenUseEffectCleanup useEffect destroy',
|
||||
);
|
||||
throw new Error('Expected error');
|
||||
};
|
||||
}, []);
|
||||
|
||||
return 'inner child';
|
||||
};
|
||||
|
||||
ErrorBoundary = class extends React.Component {
|
||||
state = {error: null};
|
||||
static getDerivedStateFromError(error) {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`ErrorBoundary static getDerivedStateFromError`,
|
||||
);
|
||||
return {error};
|
||||
}
|
||||
componentDidCatch(error, info) {
|
||||
Scheduler.unstable_yieldValue(`ErrorBoundary componentDidCatch`);
|
||||
}
|
||||
render() {
|
||||
if (this.state.error) {
|
||||
Scheduler.unstable_yieldValue('ErrorBoundary render error');
|
||||
return <span prop="ErrorBoundary fallback" />;
|
||||
}
|
||||
Scheduler.unstable_yieldValue('ErrorBoundary render success');
|
||||
return this.props.children || null;
|
||||
}
|
||||
};
|
||||
|
||||
DerivedStateOnlyErrorBoundary = class extends React.Component {
|
||||
state = {error: null};
|
||||
static getDerivedStateFromError(error) {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`DerivedStateOnlyErrorBoundary static getDerivedStateFromError`,
|
||||
);
|
||||
return {error};
|
||||
}
|
||||
render() {
|
||||
if (this.state.error) {
|
||||
Scheduler.unstable_yieldValue(
|
||||
'DerivedStateOnlyErrorBoundary render error',
|
||||
);
|
||||
return <span prop="DerivedStateOnlyErrorBoundary fallback" />;
|
||||
}
|
||||
Scheduler.unstable_yieldValue(
|
||||
'DerivedStateOnlyErrorBoundary render success',
|
||||
);
|
||||
return this.props.children || null;
|
||||
}
|
||||
};
|
||||
|
||||
LogOnlyErrorBoundary = class extends React.Component {
|
||||
componentDidCatch(error, info) {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`LogOnlyErrorBoundary componentDidCatch`,
|
||||
);
|
||||
}
|
||||
render() {
|
||||
Scheduler.unstable_yieldValue(`LogOnlyErrorBoundary render`);
|
||||
return this.props.children || null;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// @gate old
|
||||
it('should call componentDidCatch() for the nearest unmounted log-only boundary', () => {
|
||||
function Conditional({showChildren}) {
|
||||
if (showChildren) {
|
||||
return (
|
||||
<LogOnlyErrorBoundary>
|
||||
<BrokenUseEffectCleanup />
|
||||
</LogOnlyErrorBoundary>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
act(() => {
|
||||
ReactNoop.render(
|
||||
<ErrorBoundary>
|
||||
<Conditional showChildren={true} />
|
||||
</ErrorBoundary>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'ErrorBoundary render success',
|
||||
'LogOnlyErrorBoundary render',
|
||||
'BrokenUseEffectCleanup useEffect',
|
||||
]);
|
||||
|
||||
act(() => {
|
||||
ReactNoop.render(
|
||||
<ErrorBoundary>
|
||||
<Conditional showChildren={false} />
|
||||
</ErrorBoundary>,
|
||||
);
|
||||
expect(Scheduler).toFlushAndYieldThrough([
|
||||
'ErrorBoundary render success',
|
||||
]);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'BrokenUseEffectCleanup useEffect destroy',
|
||||
'LogOnlyErrorBoundary componentDidCatch',
|
||||
]);
|
||||
});
|
||||
|
||||
// @gate old
|
||||
it('should call componentDidCatch() for the nearest unmounted logging-capable boundary', () => {
|
||||
function Conditional({showChildren}) {
|
||||
if (showChildren) {
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<BrokenUseEffectCleanup />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
act(() => {
|
||||
ReactNoop.render(
|
||||
<ErrorBoundary>
|
||||
<Conditional showChildren={true} />
|
||||
</ErrorBoundary>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'ErrorBoundary render success',
|
||||
'ErrorBoundary render success',
|
||||
'BrokenUseEffectCleanup useEffect',
|
||||
]);
|
||||
|
||||
act(() => {
|
||||
ReactNoop.render(
|
||||
<ErrorBoundary>
|
||||
<Conditional showChildren={false} />
|
||||
</ErrorBoundary>,
|
||||
);
|
||||
expect(Scheduler).toFlushAndYieldThrough([
|
||||
'ErrorBoundary render success',
|
||||
]);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'BrokenUseEffectCleanup useEffect destroy',
|
||||
'ErrorBoundary componentDidCatch',
|
||||
]);
|
||||
});
|
||||
|
||||
// @gate old
|
||||
it('should not call getDerivedStateFromError for unmounted error boundaries', () => {
|
||||
function Conditional({showChildren}) {
|
||||
if (showChildren) {
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<BrokenUseEffectCleanup />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
act(() => {
|
||||
ReactNoop.render(<Conditional showChildren={true} />);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'ErrorBoundary render success',
|
||||
'BrokenUseEffectCleanup useEffect',
|
||||
]);
|
||||
|
||||
act(() => {
|
||||
ReactNoop.render(<Conditional showChildren={false} />);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'BrokenUseEffectCleanup useEffect destroy',
|
||||
'ErrorBoundary componentDidCatch',
|
||||
]);
|
||||
});
|
||||
|
||||
// @gate old
|
||||
it('should not throw if there are no unmounted logging-capable boundaries to call', () => {
|
||||
function Conditional({showChildren}) {
|
||||
if (showChildren) {
|
||||
return (
|
||||
<DerivedStateOnlyErrorBoundary>
|
||||
<BrokenUseEffectCleanup />
|
||||
</DerivedStateOnlyErrorBoundary>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
act(() => {
|
||||
ReactNoop.render(<Conditional showChildren={true} />);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'DerivedStateOnlyErrorBoundary render success',
|
||||
'BrokenUseEffectCleanup useEffect',
|
||||
]);
|
||||
|
||||
act(() => {
|
||||
ReactNoop.render(<Conditional showChildren={false} />);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'BrokenUseEffectCleanup useEffect destroy',
|
||||
]);
|
||||
});
|
||||
|
||||
// @gate new
|
||||
it('should use the nearest still-mounted boundary if there are no unmounted boundaries', () => {
|
||||
act(() => {
|
||||
ReactNoop.render(
|
||||
<LogOnlyErrorBoundary>
|
||||
<BrokenUseEffectCleanup />
|
||||
</LogOnlyErrorBoundary>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'LogOnlyErrorBoundary render',
|
||||
'BrokenUseEffectCleanup useEffect',
|
||||
]);
|
||||
|
||||
act(() => {
|
||||
ReactNoop.render(<LogOnlyErrorBoundary />);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'LogOnlyErrorBoundary render',
|
||||
'BrokenUseEffectCleanup useEffect destroy',
|
||||
'LogOnlyErrorBoundary componentDidCatch',
|
||||
]);
|
||||
});
|
||||
|
||||
// @gate new
|
||||
it('should skip unmounted boundaries and use the nearest still-mounted boundary', () => {
|
||||
function Conditional({showChildren}) {
|
||||
if (showChildren) {
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<BrokenUseEffectCleanup />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
act(() => {
|
||||
ReactNoop.render(
|
||||
<LogOnlyErrorBoundary>
|
||||
<Conditional showChildren={true} />
|
||||
</LogOnlyErrorBoundary>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'LogOnlyErrorBoundary render',
|
||||
'ErrorBoundary render success',
|
||||
'BrokenUseEffectCleanup useEffect',
|
||||
]);
|
||||
|
||||
act(() => {
|
||||
ReactNoop.render(
|
||||
<LogOnlyErrorBoundary>
|
||||
<Conditional showChildren={false} />
|
||||
</LogOnlyErrorBoundary>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'LogOnlyErrorBoundary render',
|
||||
'BrokenUseEffectCleanup useEffect destroy',
|
||||
'LogOnlyErrorBoundary componentDidCatch',
|
||||
]);
|
||||
});
|
||||
|
||||
// @gate new
|
||||
it('should call getDerivedStateFromError in the nearest still-mounted boundary', () => {
|
||||
function Conditional({showChildren}) {
|
||||
if (showChildren) {
|
||||
return <BrokenUseEffectCleanup />;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
act(() => {
|
||||
ReactNoop.render(
|
||||
<ErrorBoundary>
|
||||
<Conditional showChildren={true} />
|
||||
</ErrorBoundary>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'ErrorBoundary render success',
|
||||
'BrokenUseEffectCleanup useEffect',
|
||||
]);
|
||||
|
||||
act(() => {
|
||||
ReactNoop.render(
|
||||
<ErrorBoundary>
|
||||
<Conditional showChildren={false} />
|
||||
</ErrorBoundary>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'ErrorBoundary render success',
|
||||
'BrokenUseEffectCleanup useEffect destroy',
|
||||
'ErrorBoundary static getDerivedStateFromError',
|
||||
'ErrorBoundary render error',
|
||||
'ErrorBoundary componentDidCatch',
|
||||
]);
|
||||
|
||||
expect(ReactNoop.getChildren()).toEqual([
|
||||
span('ErrorBoundary fallback'),
|
||||
]);
|
||||
});
|
||||
|
||||
// @gate new
|
||||
it('should rethrow error if there are no still-mounted boundaries', () => {
|
||||
function Conditional({showChildren}) {
|
||||
if (showChildren) {
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<BrokenUseEffectCleanup />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
act(() => {
|
||||
ReactNoop.render(<Conditional showChildren={true} />);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'ErrorBoundary render success',
|
||||
'BrokenUseEffectCleanup useEffect',
|
||||
]);
|
||||
|
||||
expect(() => {
|
||||
act(() => {
|
||||
ReactNoop.render(<Conditional showChildren={false} />);
|
||||
});
|
||||
}).toThrow('Expected error');
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'BrokenUseEffectCleanup useEffect destroy',
|
||||
]);
|
||||
|
||||
expect(ReactNoop.getChildren()).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
it('calls passive effect destroy functions for memoized components', () => {
|
||||
const Wrapper = ({children}) => children;
|
||||
function Child() {
|
||||
@@ -2971,85 +2589,6 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||
'Mount normal [current: 1]',
|
||||
]);
|
||||
});
|
||||
|
||||
// @gate skipUnmountedBoundaries
|
||||
it('catches errors thrown in useLayoutEffect', () => {
|
||||
class ErrorBoundary extends React.Component {
|
||||
state = {error: null};
|
||||
static getDerivedStateFromError(error) {
|
||||
Scheduler.unstable_yieldValue(
|
||||
`ErrorBoundary static getDerivedStateFromError`,
|
||||
);
|
||||
return {error};
|
||||
}
|
||||
render() {
|
||||
const {children, id, fallbackID} = this.props;
|
||||
const {error} = this.state;
|
||||
if (error) {
|
||||
Scheduler.unstable_yieldValue(`${id} render error`);
|
||||
return <Component id={fallbackID} />;
|
||||
}
|
||||
Scheduler.unstable_yieldValue(`${id} render success`);
|
||||
return children || null;
|
||||
}
|
||||
}
|
||||
|
||||
function Component({id}) {
|
||||
Scheduler.unstable_yieldValue('Component render ' + id);
|
||||
return <span prop={id} />;
|
||||
}
|
||||
|
||||
function BrokenLayoutEffectDestroy() {
|
||||
useLayoutEffect(() => {
|
||||
return () => {
|
||||
Scheduler.unstable_yieldValue(
|
||||
'BrokenLayoutEffectDestroy useLayoutEffect destroy',
|
||||
);
|
||||
throw Error('Expected');
|
||||
};
|
||||
}, []);
|
||||
|
||||
Scheduler.unstable_yieldValue('BrokenLayoutEffectDestroy render');
|
||||
return <span prop="broken" />;
|
||||
}
|
||||
|
||||
ReactNoop.render(
|
||||
<ErrorBoundary id="OuterBoundary" fallbackID="OuterFallback">
|
||||
<Component id="sibling" />
|
||||
<ErrorBoundary id="InnerBoundary" fallbackID="InnerFallback">
|
||||
<BrokenLayoutEffectDestroy />
|
||||
</ErrorBoundary>
|
||||
</ErrorBoundary>,
|
||||
);
|
||||
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
'OuterBoundary render success',
|
||||
'Component render sibling',
|
||||
'InnerBoundary render success',
|
||||
'BrokenLayoutEffectDestroy render',
|
||||
]);
|
||||
expect(ReactNoop.getChildren()).toEqual([
|
||||
span('sibling'),
|
||||
span('broken'),
|
||||
]);
|
||||
|
||||
ReactNoop.render(
|
||||
<ErrorBoundary id="OuterBoundary" fallbackID="OuterFallback">
|
||||
<Component id="sibling" />
|
||||
</ErrorBoundary>,
|
||||
);
|
||||
|
||||
// React should skip over the unmounting boundary and find the nearest still-mounted boundary.
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
'OuterBoundary render success',
|
||||
'Component render sibling',
|
||||
'BrokenLayoutEffectDestroy useLayoutEffect destroy',
|
||||
'ErrorBoundary static getDerivedStateFromError',
|
||||
'OuterBoundary render error',
|
||||
'Component render OuterFallback',
|
||||
]);
|
||||
expect(ReactNoop.getChildren()).toEqual([span('OuterFallback')]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useCallback', () => {
|
||||
|
||||
+6
-12
@@ -961,7 +961,6 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||
expect(Scheduler).toFlushAndYield(['Foo']);
|
||||
});
|
||||
|
||||
// @gate skipUnmountedBoundaries
|
||||
it('should not attempt to recover an unmounting error boundary', () => {
|
||||
class Parent extends React.Component {
|
||||
componentWillUnmount() {
|
||||
@@ -993,17 +992,12 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||
|
||||
ReactNoop.render(<Parent />);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
|
||||
// Because the error boundary is also unmounting,
|
||||
// an error in ThrowsOnUnmount should be rethrown.
|
||||
expect(() => {
|
||||
ReactNoop.render(null);
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
'Parent componentWillUnmount',
|
||||
'ThrowsOnUnmount componentWillUnmount',
|
||||
]);
|
||||
}).toThrow('unmount error');
|
||||
|
||||
ReactNoop.render(null);
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
// Parent unmounts before the error is thrown.
|
||||
'Parent componentWillUnmount',
|
||||
'ThrowsOnUnmount componentWillUnmount',
|
||||
]);
|
||||
ReactNoop.render(<Parent />);
|
||||
});
|
||||
|
||||
|
||||
@@ -91,12 +91,6 @@ export const enableComponentStackLocations = true;
|
||||
|
||||
export const enableNewReconciler = false;
|
||||
|
||||
// Errors that are thrown while unmounting (or after in the case of passive effects)
|
||||
// should bypass any error boundaries that are also unmounting (or have unmounted)
|
||||
// and be handled by the nearest still-mounted boundary.
|
||||
// If there are no still-mounted boundaries, the errors should be rethrown.
|
||||
export const skipUnmountedBoundaries = false;
|
||||
|
||||
// --------------------------
|
||||
// Future APIs to be deprecated
|
||||
// --------------------------
|
||||
|
||||
@@ -42,7 +42,6 @@ export const warnAboutSpreadingKeyToJSX = false;
|
||||
export const enableComponentStackLocations = false;
|
||||
export const enableLegacyFBSupport = false;
|
||||
export const enableFilterEmptyStringAttributesDOM = false;
|
||||
export const skipUnmountedBoundaries = false;
|
||||
|
||||
export const enableNewReconciler = false;
|
||||
export const deferRenderPhaseUpdateToNextBatch = true;
|
||||
|
||||
@@ -41,7 +41,6 @@ export const warnAboutSpreadingKeyToJSX = false;
|
||||
export const enableComponentStackLocations = false;
|
||||
export const enableLegacyFBSupport = false;
|
||||
export const enableFilterEmptyStringAttributesDOM = false;
|
||||
export const skipUnmountedBoundaries = false;
|
||||
|
||||
export const enableNewReconciler = false;
|
||||
export const deferRenderPhaseUpdateToNextBatch = true;
|
||||
|
||||
@@ -41,7 +41,6 @@ export const warnAboutSpreadingKeyToJSX = false;
|
||||
export const enableComponentStackLocations = true;
|
||||
export const enableLegacyFBSupport = false;
|
||||
export const enableFilterEmptyStringAttributesDOM = false;
|
||||
export const skipUnmountedBoundaries = false;
|
||||
|
||||
export const enableNewReconciler = false;
|
||||
export const deferRenderPhaseUpdateToNextBatch = true;
|
||||
|
||||
@@ -41,7 +41,6 @@ export const warnAboutSpreadingKeyToJSX = false;
|
||||
export const enableComponentStackLocations = false;
|
||||
export const enableLegacyFBSupport = false;
|
||||
export const enableFilterEmptyStringAttributesDOM = false;
|
||||
export const skipUnmountedBoundaries = false;
|
||||
|
||||
export const enableNewReconciler = false;
|
||||
export const deferRenderPhaseUpdateToNextBatch = true;
|
||||
|
||||
@@ -41,7 +41,6 @@ export const warnAboutSpreadingKeyToJSX = false;
|
||||
export const enableComponentStackLocations = true;
|
||||
export const enableLegacyFBSupport = false;
|
||||
export const enableFilterEmptyStringAttributesDOM = false;
|
||||
export const skipUnmountedBoundaries = false;
|
||||
|
||||
export const enableNewReconciler = false;
|
||||
export const deferRenderPhaseUpdateToNextBatch = true;
|
||||
|
||||
@@ -41,7 +41,6 @@ export const warnAboutSpreadingKeyToJSX = false;
|
||||
export const enableComponentStackLocations = true;
|
||||
export const enableLegacyFBSupport = false;
|
||||
export const enableFilterEmptyStringAttributesDOM = false;
|
||||
export const skipUnmountedBoundaries = false;
|
||||
|
||||
export const enableNewReconciler = false;
|
||||
export const deferRenderPhaseUpdateToNextBatch = true;
|
||||
|
||||
@@ -41,7 +41,6 @@ export const warnAboutSpreadingKeyToJSX = false;
|
||||
export const enableComponentStackLocations = true;
|
||||
export const enableLegacyFBSupport = !__EXPERIMENTAL__;
|
||||
export const enableFilterEmptyStringAttributesDOM = false;
|
||||
export const skipUnmountedBoundaries = __EXPERIMENTAL__;
|
||||
|
||||
export const enableNewReconciler = false;
|
||||
export const deferRenderPhaseUpdateToNextBatch = true;
|
||||
|
||||
@@ -18,7 +18,6 @@ export const disableInputAttributeSyncing = __VARIANT__;
|
||||
export const enableFilterEmptyStringAttributesDOM = __VARIANT__;
|
||||
export const enableLegacyFBSupport = __VARIANT__;
|
||||
export const decoupleUpdatePriorityFromScheduler = __VARIANT__;
|
||||
export const skipUnmountedBoundaries = __VARIANT__;
|
||||
|
||||
// Enable this flag to help with concurrent mode debugging.
|
||||
// It logs information to the console about React scheduling, rendering, and commit phases.
|
||||
|
||||
@@ -26,7 +26,6 @@ export const {
|
||||
deferRenderPhaseUpdateToNextBatch,
|
||||
decoupleUpdatePriorityFromScheduler,
|
||||
enableDebugTracing,
|
||||
skipUnmountedBoundaries,
|
||||
enableDoubleInvokingEffects,
|
||||
enableUseRefAccessWarning,
|
||||
} = dynamicFeatureFlags;
|
||||
|
||||
Reference in New Issue
Block a user