Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,6 @@ describe('createSubscription', () => {

it('should ignore values emitted by a new subscribable until the commit phase', () => {
const log = [];
let parentInstance;

function Child({value}) {
ReactNoop.yield('Child: ' + value);
Expand Down Expand Up @@ -301,8 +300,6 @@ describe('createSubscription', () => {
}

render() {
parentInstance = this;

return (
<Subscription source={this.state.observed}>
{(value = 'default') => {
Expand Down Expand Up @@ -331,8 +328,8 @@ describe('createSubscription', () => {
observableB.next('b-2');
observableB.next('b-3');

// Mimic a higher-priority interruption
parentInstance.setState({observed: observableA});
// Update again
ReactNoop.render(<Parent observed={observableA} />);

// Flush everything and ensure that the correct subscribable is used
// We expect the last emitted update to be rendered (because of the commit phase value check)
Expand All @@ -354,7 +351,6 @@ describe('createSubscription', () => {

it('should not drop values emitted between updates', () => {
const log = [];
let parentInstance;

function Child({value}) {
ReactNoop.yield('Child: ' + value);
Expand Down Expand Up @@ -391,8 +387,6 @@ describe('createSubscription', () => {
}

render() {
parentInstance = this;

return (
<Subscription source={this.state.observed}>
{(value = 'default') => {
Expand Down Expand Up @@ -420,8 +414,8 @@ describe('createSubscription', () => {
observableA.next('a-1');
observableA.next('a-2');

// Mimic a higher-priority interruption
parentInstance.setState({observed: observableA});
// Update again
ReactNoop.render(<Parent observed={observableA} />);

// Flush everything and ensure that the correct subscribable is used
// We expect the new subscribable to finish rendering,
Expand Down
16 changes: 4 additions & 12 deletions packages/react-noop-renderer/src/ReactNoop.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/

import type {Fiber} from 'react-reconciler/src/ReactFiber';
import type {UpdateQueue} from 'react-reconciler/src/ReactFiberUpdateQueue';
import type {UpdateQueue} from 'react-reconciler/src/ReactUpdateQueue';
import type {ReactNodeList} from 'shared/ReactTypes';
import ReactFiberReconciler from 'react-reconciler';
import {enablePersistentReconciler} from 'shared/ReactFeatureFlags';
Expand Down Expand Up @@ -526,23 +526,15 @@ const ReactNoop = {

function logUpdateQueue(updateQueue: UpdateQueue<mixed>, depth) {
log(' '.repeat(depth + 1) + 'QUEUED UPDATES');
const firstUpdate = updateQueue.first;
const firstUpdate = updateQueue.firstUpdate;
if (!firstUpdate) {
return;
}

log(
' '.repeat(depth + 1) + '~',
firstUpdate && firstUpdate.partialState,
firstUpdate.callback ? 'with callback' : '',
'[' + firstUpdate.expirationTime + ']',
);
let next;
while ((next = firstUpdate.next)) {
log(' '.repeat(depth + 1) + '~', '[' + firstUpdate.expirationTime + ']');
while (firstUpdate.next) {
log(
' '.repeat(depth + 1) + '~',
next.partialState,
next.callback ? 'with callback' : '',
'[' + firstUpdate.expirationTime + ']',
);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/react-reconciler/src/ReactFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import type {TypeOfWork} from 'shared/ReactTypeOfWork';
import type {TypeOfMode} from './ReactTypeOfMode';
import type {TypeOfSideEffect} from 'shared/ReactTypeOfSideEffect';
import type {ExpirationTime} from './ReactFiberExpirationTime';
import type {UpdateQueue} from './ReactFiberUpdateQueue';
import type {UpdateQueue} from './ReactUpdateQueue';

import invariant from 'fbjs/lib/invariant';
import {NoEffect} from 'shared/ReactTypeOfSideEffect';
Expand Down
80 changes: 28 additions & 52 deletions packages/react-reconciler/src/ReactFiberBeginWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,12 @@ import {
ContextConsumer,
} from 'shared/ReactTypeOfWork';
import {
NoEffect,
PerformedWork,
Placement,
ContentReset,
Ref,
DidCapture,
} from 'shared/ReactTypeOfSideEffect';
import {ReactCurrentOwner} from 'shared/ReactGlobalSharedState';
import {
Expand All @@ -52,13 +54,15 @@ import warning from 'fbjs/lib/warning';
import ReactDebugCurrentFiber from './ReactDebugCurrentFiber';
import {cancelWorkTimer} from './ReactDebugFiberPerf';

import ReactFiberClassComponent from './ReactFiberClassComponent';
import ReactFiberClassComponent, {
applyDerivedStateFromProps,
} from './ReactFiberClassComponent';
import {
mountChildFibers,
reconcileChildFibers,
cloneChildFibers,
} from './ReactChildFiber';
import {processUpdateQueue} from './ReactFiberUpdateQueue';
import {processUpdateQueue} from './ReactUpdateQueue';
import {NoWork, Never} from './ReactFiberExpirationTime';
import {AsyncMode, StrictMode} from './ReactTypeOfMode';
import MAX_SIGNED_31_BIT_INT from './maxSigned31BitInt';
Expand Down Expand Up @@ -105,7 +109,6 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(

const {
adoptClassInstance,
callGetDerivedStateFromProps,
constructClassInstance,
mountClassInstance,
resumeMountClassInstance,
Expand Down Expand Up @@ -260,7 +263,11 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
if (current === null) {
if (workInProgress.stateNode === null) {
// In the initial pass we might need to construct the instance.
constructClassInstance(workInProgress, workInProgress.pendingProps);
constructClassInstance(
workInProgress,
workInProgress.pendingProps,
renderExpirationTime,
);
mountClassInstance(workInProgress, renderExpirationTime);

shouldUpdate = true;
Expand All @@ -278,22 +285,11 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
renderExpirationTime,
);
}

// We processed the update queue inside updateClassInstance. It may have
// included some errors that were dispatched during the commit phase.
// TODO: Refactor class components so this is less awkward.
let didCaptureError = false;
const updateQueue = workInProgress.updateQueue;
if (updateQueue !== null && updateQueue.capturedValues !== null) {
shouldUpdate = true;
didCaptureError = true;
}
return finishClassComponent(
current,
workInProgress,
shouldUpdate,
hasContext,
didCaptureError,
renderExpirationTime,
);
}
Expand All @@ -303,12 +299,14 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
workInProgress: Fiber,
shouldUpdate: boolean,
hasContext: boolean,
didCaptureError: boolean,
renderExpirationTime: ExpirationTime,
) {
// Refs should update even if shouldComponentUpdate returns false
markRef(current, workInProgress);

const didCaptureError =
(workInProgress.effectTag & DidCapture) !== NoEffect;

if (!shouldUpdate && !didCaptureError) {
// Context providers should defer to sCU for rendering
if (hasContext) {
Expand Down Expand Up @@ -348,13 +346,6 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
}
ReactDebugCurrentFiber.setCurrentPhase(null);
} else {
if (
debugRenderPhaseSideEffects ||
(debugRenderPhaseSideEffectsForStrictMode &&
workInProgress.mode & StrictMode)
) {
instance.render();
}
nextChildren = instance.render();
}
}
Expand Down Expand Up @@ -413,29 +404,24 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
pushHostRootContext(workInProgress);
let updateQueue = workInProgress.updateQueue;
if (updateQueue !== null) {
const nextProps = workInProgress.pendingProps;
const prevState = workInProgress.memoizedState;
const state = processUpdateQueue(
current,
const prevChildren = prevState !== null ? prevState.children : null;
processUpdateQueue(
workInProgress,
updateQueue,
null,
nextProps,
null,
renderExpirationTime,
);
memoizeState(workInProgress, state);
updateQueue = workInProgress.updateQueue;

let element;
if (updateQueue !== null && updateQueue.capturedValues !== null) {
// There's an uncaught error. Unmount the whole root.
element = null;
} else if (prevState === state) {
const nextState = workInProgress.memoizedState;
const nextChildren = nextState.children;

if (nextChildren === prevChildren) {
// If the state is the same as before, that's a bailout because we had
// no work that expires at this time.
resetHydrationState();
return bailoutOnAlreadyFinishedWork(current, workInProgress);
} else {
element = state.element;
}
const root: FiberRoot = workInProgress.stateNode;
if (
Expand All @@ -460,16 +446,15 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
workInProgress.child = mountChildFibers(
workInProgress,
null,
element,
nextChildren,
renderExpirationTime,
);
} else {
// Otherwise reset hydration state in case we aborted and resumed another
// root.
resetHydrationState();
reconcileChildren(current, workInProgress, element);
reconcileChildren(current, workInProgress, nextChildren);
}
memoizeState(workInProgress, state);
return workInProgress.child;
}
resetHydrationState();
Expand Down Expand Up @@ -607,21 +592,13 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
workInProgress.memoizedState =
value.state !== null && value.state !== undefined ? value.state : null;

if (typeof Component.getDerivedStateFromProps === 'function') {
const partialState = callGetDerivedStateFromProps(
const getDerivedStateFromProps = Component.getDerivedStateFromProps;
if (typeof getDerivedStateFromProps === 'function') {
applyDerivedStateFromProps(
workInProgress,
value,
getDerivedStateFromProps,
props,
workInProgress.memoizedState,
);

if (partialState !== null && partialState !== undefined) {
workInProgress.memoizedState = Object.assign(
{},
workInProgress.memoizedState,
partialState,
);
}
}

// Push context providers early to prevent context stack mismatches.
Expand All @@ -635,7 +612,6 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
workInProgress,
true,
hasContext,
false,
renderExpirationTime,
);
} else {
Expand Down
Loading