Clear effect tags from a fiber that suspends in non-concurrent mode

Even though we commit the fiber in an incomplete state, we shouldn't
fire any lifecycles or effects.

We already did this for classes, but now with useEffect, the same is
needed for other types of work, too.
This commit is contained in:
Andrew Clark
2018-10-29 11:26:54 -07:00
parent 173c5579a8
commit fe2c9746f3
2 changed files with 52 additions and 5 deletions
+6 -5
View File
@@ -248,15 +248,16 @@ function throwException(
);
sourceFiber.effectTag &= ~Incomplete;
// We're going to commit this fiber even though it didn't complete.
// But we shouldn't call any lifecycle methods or callbacks. Remove
// all lifecycle effect tags.
sourceFiber.effectTag &= ~LifecycleEffectMask;
if (sourceFiber.tag === ClassComponent) {
// We're going to commit this fiber even though it didn't complete.
// But we shouldn't call any lifecycle methods or callbacks. Remove
// all lifecycle effect tags.
sourceFiber.effectTag &= ~LifecycleEffectMask;
const current = sourceFiber.alternate;
if (current === null) {
// This is a new mount. Change the tag so it's not mistaken for a
// completed component. For example, we should not call
// completed class component. For example, we should not call
// componentWillUnmount if it is deleted.
sourceFiber.tag = IncompleteClassComponent;
}
@@ -17,6 +17,7 @@ describe('ReactSuspense', () => {
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
ReactFeatureFlags.enableHooks = true;
React = require('react');
ReactTestRenderer = require('react-test-renderer');
// JestReact = require('jest-react');
@@ -526,6 +527,51 @@ describe('ReactSuspense', () => {
expect(root).toMatchRenderedOutput('B');
});
it('suspends in a component that also contains useEffect', () => {
const {useLayoutEffect} = React;
function AsyncTextWithEffect(props) {
const text = props.text;
useLayoutEffect(
() => {
ReactTestRenderer.unstable_yield('Did commit: ' + text);
},
[text],
);
try {
TextResource.read([props.text, props.ms]);
ReactTestRenderer.unstable_yield(text);
return text;
} catch (promise) {
if (typeof promise.then === 'function') {
ReactTestRenderer.unstable_yield(`Suspend! [${text}]`);
} else {
ReactTestRenderer.unstable_yield(`Error! [${text}]`);
}
throw promise;
}
}
function App({text}) {
return (
<Suspense fallback={<Text text="Loading..." />}>
<AsyncTextWithEffect text={text} ms={100} />
</Suspense>
);
}
ReactTestRenderer.create(<App text="A" />);
expect(ReactTestRenderer).toHaveYielded(['Suspend! [A]', 'Loading...']);
jest.advanceTimersByTime(500);
expect(ReactTestRenderer).toHaveYielded([
'Promise resolved [A]',
'A',
'Did commit: A',
]);
});
it('retries when an update is scheduled on a timed out tree', () => {
let instance;
class Stateful extends React.Component {