Skip to content

Commit

Permalink
Context.unstable_read
Browse files Browse the repository at this point in the history
unstable_read can be called anywhere within the render phase. That
includes the render method, getDerivedStateFromProps, constructors,
functional components, and context consumer render props.

If it's called outside the render phase, an error is thrown.
  • Loading branch information
acdlite committed Jul 2, 2018
1 parent 78be8f6 commit a2a2793
Show file tree
Hide file tree
Showing 8 changed files with 1,266 additions and 987 deletions.
41 changes: 31 additions & 10 deletions packages/react-reconciler/src/ReactFiberBeginWork.js
Expand Up @@ -239,26 +239,33 @@ function markRef(current: Fiber | null, workInProgress: Fiber) {
}
}

function updateFunctionalComponent(current, workInProgress) {
function updateFunctionalComponent(
current,
workInProgress,
renderExpirationTime,
) {
const fn = workInProgress.type;
const nextProps = workInProgress.pendingProps;

if (hasLegacyContextChanged()) {
// Normally we can bail out on props equality but if context has changed
// we don't do the bailout and we have to reuse existing props instead.
} else {
if (workInProgress.memoizedProps === nextProps) {
return bailoutOnAlreadyFinishedWork(current, workInProgress);
if (!checkForPendingContext(workInProgress, renderExpirationTime)) {
if (hasLegacyContextChanged()) {
// Normally we can bail out on props equality but if context has changed
// we don't do the bailout and we have to reuse existing props instead.
} else {
if (workInProgress.memoizedProps === nextProps) {
return bailoutOnAlreadyFinishedWork(current, workInProgress);
}
// TODO: consider bringing fn.shouldComponentUpdate() back.
// It used to be here.
}
// TODO: consider bringing fn.shouldComponentUpdate() back.
// It used to be here.
}

const unmaskedContext = getUnmaskedContext(workInProgress);
const context = getMaskedContext(workInProgress, unmaskedContext);

let nextChildren;

prepareToReadContext();
if (__DEV__) {
ReactCurrentOwner.current = workInProgress;
ReactDebugCurrentFiber.setCurrentPhase('render');
Expand All @@ -267,6 +274,8 @@ function updateFunctionalComponent(current, workInProgress) {
} else {
nextChildren = fn(nextProps, context);
}
workInProgress.firstContextReader = finishReadingContext();

// React DevTools reads this flag.
workInProgress.effectTag |= PerformedWork;
reconcileChildren(current, workInProgress, nextChildren);
Expand All @@ -283,6 +292,8 @@ function updateClassComponent(
// During mounting we don't know the child context yet as the instance doesn't exist.
// We will invalidate the child context in finishClassComponent() right after rendering.
const hasContext = pushLegacyContextProvider(workInProgress);
prepareToReadContext();

let shouldUpdate;
if (current === null) {
if (workInProgress.stateNode === null) {
Expand Down Expand Up @@ -377,6 +388,8 @@ function finishClassComponent(
}
}

workInProgress.firstContextReader = finishReadingContext();

// React DevTools reads this flag.
workInProgress.effectTag |= PerformedWork;
if (didCaptureError) {
Expand Down Expand Up @@ -582,6 +595,8 @@ function mountIndeterminateComponent(
const unmaskedContext = getUnmaskedContext(workInProgress);
const context = getMaskedContext(workInProgress, unmaskedContext);

prepareToReadContext();

let value;

if (__DEV__) {
Expand Down Expand Up @@ -612,6 +627,8 @@ function mountIndeterminateComponent(
// React DevTools reads this flag.
workInProgress.effectTag |= PerformedWork;

workInProgress.firstContextReader = finishReadingContext();

if (
typeof value === 'object' &&
value !== null &&
Expand Down Expand Up @@ -1035,7 +1052,11 @@ function beginWork(
renderExpirationTime,
);
case FunctionalComponent:
return updateFunctionalComponent(current, workInProgress);
return updateFunctionalComponent(
current,
workInProgress,
renderExpirationTime,
);
case ClassComponent:
return updateClassComponent(
current,
Expand Down
63 changes: 44 additions & 19 deletions packages/react-reconciler/src/ReactFiberClassComponent.js
Expand Up @@ -50,6 +50,7 @@ import {
computeExpirationForFiber,
scheduleWork,
} from './ReactFiberScheduler';
import {checkForPendingContext} from './ReactFiberNewContext';

const fakeInternalInstance = {};
const isArray = Array.isArray;
Expand Down Expand Up @@ -231,7 +232,7 @@ function checkShouldComponentUpdate(
newProps,
oldState,
newState,
newContext,
nextLegacyContext,
) {
const instance = workInProgress.stateNode;
const ctor = workInProgress.type;
Expand All @@ -240,7 +241,7 @@ function checkShouldComponentUpdate(
const shouldUpdate = instance.shouldComponentUpdate(
newProps,
newState,
newContext,
nextLegacyContext,
);
stopPhaseTimer();

Expand Down Expand Up @@ -616,15 +617,15 @@ function callComponentWillReceiveProps(
workInProgress,
instance,
newProps,
newContext,
nextLegacyContext,
) {
const oldState = instance.state;
startPhaseTimer(workInProgress, 'componentWillReceiveProps');
if (typeof instance.componentWillReceiveProps === 'function') {
instance.componentWillReceiveProps(newProps, newContext);
instance.componentWillReceiveProps(newProps, nextLegacyContext);
}
if (typeof instance.UNSAFE_componentWillReceiveProps === 'function') {
instance.UNSAFE_componentWillReceiveProps(newProps, newContext);
instance.UNSAFE_componentWillReceiveProps(newProps, nextLegacyContext);
}
stopPhaseTimer();

Expand Down Expand Up @@ -746,8 +747,16 @@ function resumeMountClassInstance(
instance.props = oldProps;

const oldContext = instance.context;
const newUnmaskedContext = getUnmaskedContext(workInProgress);
const newContext = getMaskedContext(workInProgress, newUnmaskedContext);
const nextLegacyUnmaskedContext = getUnmaskedContext(workInProgress);
const nextLegacyContext = getMaskedContext(
workInProgress,
nextLegacyUnmaskedContext,
);

const hasPendingNewContext = checkForPendingContext(
workInProgress,
renderExpirationTime,
);

const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
const hasNewLifecycles =
Expand All @@ -765,12 +774,12 @@ function resumeMountClassInstance(
(typeof instance.UNSAFE_componentWillReceiveProps === 'function' ||
typeof instance.componentWillReceiveProps === 'function')
) {
if (oldProps !== newProps || oldContext !== newContext) {
if (oldProps !== newProps || oldContext !== nextLegacyContext) {
callComponentWillReceiveProps(
workInProgress,
instance,
newProps,
newContext,
nextLegacyContext,
);
}
}
Expand All @@ -794,6 +803,7 @@ function resumeMountClassInstance(
oldProps === newProps &&
oldState === newState &&
!hasContextChanged() &&
!hasPendingNewContext &&
!checkHasForceUpdateAfterProcessing()
) {
// If an update was already in progress, we should schedule an Update
Expand All @@ -815,13 +825,14 @@ function resumeMountClassInstance(

const shouldUpdate =
checkHasForceUpdateAfterProcessing() ||
hasPendingNewContext ||
checkShouldComponentUpdate(
workInProgress,
oldProps,
newProps,
oldState,
newState,
newContext,
nextLegacyContext,
);

if (shouldUpdate) {
Expand Down Expand Up @@ -861,7 +872,7 @@ function resumeMountClassInstance(
// if shouldComponentUpdate returns false.
instance.props = newProps;
instance.state = newState;
instance.context = newContext;
instance.context = nextLegacyContext;

return shouldUpdate;
}
Expand All @@ -880,8 +891,16 @@ function updateClassInstance(
instance.props = oldProps;

const oldContext = instance.context;
const newUnmaskedContext = getUnmaskedContext(workInProgress);
const newContext = getMaskedContext(workInProgress, newUnmaskedContext);
const nextLegacyUnmaskedContext = getUnmaskedContext(workInProgress);
const nextLegacyContext = getMaskedContext(
workInProgress,
nextLegacyUnmaskedContext,
);

const hasPendingNewContext = checkForPendingContext(
workInProgress,
renderExpirationTime,
);

const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
const hasNewLifecycles =
Expand All @@ -899,12 +918,12 @@ function updateClassInstance(
(typeof instance.UNSAFE_componentWillReceiveProps === 'function' ||
typeof instance.componentWillReceiveProps === 'function')
) {
if (oldProps !== newProps || oldContext !== newContext) {
if (oldProps !== newProps || oldContext !== nextLegacyContext) {
callComponentWillReceiveProps(
workInProgress,
instance,
newProps,
newContext,
nextLegacyContext,
);
}
}
Expand All @@ -929,6 +948,7 @@ function updateClassInstance(
oldProps === newProps &&
oldState === newState &&
!hasContextChanged() &&
!hasPendingNewContext &&
!checkHasForceUpdateAfterProcessing()
) {
// If an update was already in progress, we should schedule an Update
Expand Down Expand Up @@ -963,13 +983,14 @@ function updateClassInstance(

const shouldUpdate =
checkHasForceUpdateAfterProcessing() ||
hasPendingNewContext ||
checkShouldComponentUpdate(
workInProgress,
oldProps,
newProps,
oldState,
newState,
newContext,
nextLegacyContext,
);

if (shouldUpdate) {
Expand All @@ -982,10 +1003,14 @@ function updateClassInstance(
) {
startPhaseTimer(workInProgress, 'componentWillUpdate');
if (typeof instance.componentWillUpdate === 'function') {
instance.componentWillUpdate(newProps, newState, newContext);
instance.componentWillUpdate(newProps, newState, nextLegacyContext);
}
if (typeof instance.UNSAFE_componentWillUpdate === 'function') {
instance.UNSAFE_componentWillUpdate(newProps, newState, newContext);
instance.UNSAFE_componentWillUpdate(
newProps,
newState,
nextLegacyContext,
);
}
stopPhaseTimer();
}
Expand Down Expand Up @@ -1025,7 +1050,7 @@ function updateClassInstance(
// if shouldComponentUpdate returns false.
instance.props = newProps;
instance.state = newState;
instance.context = newContext;
instance.context = nextLegacyContext;

return shouldUpdate;
}
Expand Down
14 changes: 14 additions & 0 deletions packages/react-reconciler/src/ReactFiberDispatcher.js
@@ -0,0 +1,14 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

import {readContext} from './ReactFiberNewContext';

export const Dispatcher = {
readContext,
};
3 changes: 3 additions & 0 deletions packages/react-reconciler/src/ReactFiberScheduler.js
Expand Up @@ -135,6 +135,7 @@ import {
commitAttachRef,
commitDetachRef,
} from './ReactFiberCommitWork';
import {Dispatcher} from './ReactFiberDispatcher';

export type Deadline = {
timeRemaining: () => number,
Expand Down Expand Up @@ -985,6 +986,7 @@ function renderRoot(root: FiberRoot, isYieldy: boolean): void {
'by a bug in React. Please file an issue.',
);
isWorking = true;
ReactCurrentOwner.currentDispatcher = Dispatcher;

const expirationTime = root.nextExpirationTimeToWorkOn;

Expand Down Expand Up @@ -1071,6 +1073,7 @@ function renderRoot(root: FiberRoot, isYieldy: boolean): void {

// We're done performing work. Time to clean up.
isWorking = false;
ReactCurrentOwner.currentDispatcher = null;

// Yield back to main thread.
if (didFatal) {
Expand Down

0 comments on commit a2a2793

Please sign in to comment.