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 5, 2018
1 parent d95b5f9 commit 4f7dbcf
Show file tree
Hide file tree
Showing 8 changed files with 1,252 additions and 983 deletions.
32 changes: 26 additions & 6 deletions packages/react-reconciler/src/ReactFiberBeginWork.js
Expand Up @@ -237,19 +237,25 @@ function markRef(current: Fiber | null, workInProgress: Fiber) {
}
}

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

const hasPendingContext = prepareToReadContext(
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);
}
} else if (workInProgress.memoizedProps === nextProps && !hasPendingContext) {
// TODO: consider bringing fn.shouldComponentUpdate() back.
// It used to be here.
return bailoutOnAlreadyFinishedWork(current, workInProgress);
}

const unmaskedContext = getUnmaskedContext(workInProgress);
Expand All @@ -265,6 +271,7 @@ function updateFunctionalComponent(current, workInProgress) {
} else {
nextChildren = fn(nextProps, context);
}

// React DevTools reads this flag.
workInProgress.effectTag |= PerformedWork;
reconcileChildren(current, workInProgress, nextChildren);
Expand All @@ -281,6 +288,11 @@ 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);
const hasPendingNewContext = prepareToReadContext(
workInProgress,
renderExpirationTime,
);

let shouldUpdate;
if (current === null) {
if (workInProgress.stateNode === null) {
Expand All @@ -297,13 +309,15 @@ function updateClassComponent(
// In a resume, we'll already have an instance we can reuse.
shouldUpdate = resumeMountClassInstance(
workInProgress,
hasPendingNewContext,
renderExpirationTime,
);
}
} else {
shouldUpdate = updateClassInstance(
current,
workInProgress,
hasPendingNewContext,
renderExpirationTime,
);
}
Expand Down Expand Up @@ -580,6 +594,8 @@ function mountIndeterminateComponent(
const unmaskedContext = getUnmaskedContext(workInProgress);
const context = getMaskedContext(workInProgress, unmaskedContext);

prepareToReadContext(workInProgress, renderExpirationTime);

let value;

if (__DEV__) {
Expand Down Expand Up @@ -1082,7 +1098,11 @@ function beginWork(
renderExpirationTime,
);
case FunctionalComponent:
return updateFunctionalComponent(current, workInProgress);
return updateFunctionalComponent(
current,
workInProgress,
renderExpirationTime,
);
case ClassComponent:
return updateClassComponent(
current,
Expand Down
54 changes: 35 additions & 19 deletions packages/react-reconciler/src/ReactFiberClassComponent.js
Expand Up @@ -231,7 +231,7 @@ function checkShouldComponentUpdate(
newProps,
oldState,
newState,
newContext,
nextLegacyContext,
) {
const instance = workInProgress.stateNode;
const ctor = workInProgress.type;
Expand All @@ -240,7 +240,7 @@ function checkShouldComponentUpdate(
const shouldUpdate = instance.shouldComponentUpdate(
newProps,
newState,
newContext,
nextLegacyContext,
);
stopPhaseTimer();

Expand Down Expand Up @@ -616,15 +616,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 @@ -736,6 +736,7 @@ function mountClassInstance(

function resumeMountClassInstance(
workInProgress: Fiber,
hasPendingNewContext: boolean,
renderExpirationTime: ExpirationTime,
): boolean {
const ctor = workInProgress.type;
Expand All @@ -746,8 +747,11 @@ 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 getDerivedStateFromProps = ctor.getDerivedStateFromProps;
const hasNewLifecycles =
Expand All @@ -765,12 +769,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 +798,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 +820,14 @@ function resumeMountClassInstance(

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

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

return shouldUpdate;
}
Expand All @@ -870,6 +876,7 @@ function resumeMountClassInstance(
function updateClassInstance(
current: Fiber,
workInProgress: Fiber,
hasPendingNewContext: boolean,
renderExpirationTime: ExpirationTime,
): boolean {
const ctor = workInProgress.type;
Expand All @@ -880,8 +887,11 @@ 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 getDerivedStateFromProps = ctor.getDerivedStateFromProps;
const hasNewLifecycles =
Expand All @@ -899,12 +909,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 +939,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 +974,14 @@ function updateClassInstance(

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

if (shouldUpdate) {
Expand All @@ -982,10 +994,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 +1041,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 @@ -146,6 +146,7 @@ import {
commitAttachRef,
commitDetachRef,
} from './ReactFiberCommitWork';
import {Dispatcher} from './ReactFiberDispatcher';

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

const expirationTime = root.nextExpirationTimeToWorkOn;

Expand Down Expand Up @@ -1101,6 +1103,7 @@ function renderRoot(

// 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 4f7dbcf

Please sign in to comment.