Skip to content

Commit f2670bf

Browse files
committed
Context.unstable_read
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.
1 parent 78be8f6 commit f2670bf

8 files changed

+1262
-987
lines changed

packages/react-reconciler/src/ReactFiberBeginWork.js

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -239,26 +239,33 @@ function markRef(current: Fiber | null, workInProgress: Fiber) {
239239
}
240240
}
241241

242-
function updateFunctionalComponent(current, workInProgress) {
242+
function updateFunctionalComponent(
243+
current,
244+
workInProgress,
245+
renderExpirationTime,
246+
) {
243247
const fn = workInProgress.type;
244248
const nextProps = workInProgress.pendingProps;
245249

246-
if (hasLegacyContextChanged()) {
247-
// Normally we can bail out on props equality but if context has changed
248-
// we don't do the bailout and we have to reuse existing props instead.
249-
} else {
250-
if (workInProgress.memoizedProps === nextProps) {
251-
return bailoutOnAlreadyFinishedWork(current, workInProgress);
250+
if (!checkForPendingContext(workInProgress, renderExpirationTime)) {
251+
if (hasLegacyContextChanged()) {
252+
// Normally we can bail out on props equality but if context has changed
253+
// we don't do the bailout and we have to reuse existing props instead.
254+
} else {
255+
if (workInProgress.memoizedProps === nextProps) {
256+
return bailoutOnAlreadyFinishedWork(current, workInProgress);
257+
}
258+
// TODO: consider bringing fn.shouldComponentUpdate() back.
259+
// It used to be here.
252260
}
253-
// TODO: consider bringing fn.shouldComponentUpdate() back.
254-
// It used to be here.
255261
}
256262

257263
const unmaskedContext = getUnmaskedContext(workInProgress);
258264
const context = getMaskedContext(workInProgress, unmaskedContext);
259265

260266
let nextChildren;
261267

268+
prepareToReadContext();
262269
if (__DEV__) {
263270
ReactCurrentOwner.current = workInProgress;
264271
ReactDebugCurrentFiber.setCurrentPhase('render');
@@ -267,6 +274,8 @@ function updateFunctionalComponent(current, workInProgress) {
267274
} else {
268275
nextChildren = fn(nextProps, context);
269276
}
277+
workInProgress.firstContextReader = finishReadingContext();
278+
270279
// React DevTools reads this flag.
271280
workInProgress.effectTag |= PerformedWork;
272281
reconcileChildren(current, workInProgress, nextChildren);
@@ -283,6 +292,8 @@ function updateClassComponent(
283292
// During mounting we don't know the child context yet as the instance doesn't exist.
284293
// We will invalidate the child context in finishClassComponent() right after rendering.
285294
const hasContext = pushLegacyContextProvider(workInProgress);
295+
prepareToReadContext();
296+
286297
let shouldUpdate;
287298
if (current === null) {
288299
if (workInProgress.stateNode === null) {
@@ -377,6 +388,8 @@ function finishClassComponent(
377388
}
378389
}
379390

391+
workInProgress.firstContextReader = finishReadingContext();
392+
380393
// React DevTools reads this flag.
381394
workInProgress.effectTag |= PerformedWork;
382395
if (didCaptureError) {
@@ -582,6 +595,8 @@ function mountIndeterminateComponent(
582595
const unmaskedContext = getUnmaskedContext(workInProgress);
583596
const context = getMaskedContext(workInProgress, unmaskedContext);
584597

598+
prepareToReadContext();
599+
585600
let value;
586601

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

630+
workInProgress.firstContextReader = finishReadingContext();
631+
615632
if (
616633
typeof value === 'object' &&
617634
value !== null &&
@@ -1035,7 +1052,11 @@ function beginWork(
10351052
renderExpirationTime,
10361053
);
10371054
case FunctionalComponent:
1038-
return updateFunctionalComponent(current, workInProgress);
1055+
return updateFunctionalComponent(
1056+
current,
1057+
workInProgress,
1058+
renderExpirationTime,
1059+
);
10391060
case ClassComponent:
10401061
return updateClassComponent(
10411062
current,

packages/react-reconciler/src/ReactFiberClassComponent.js

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import {
5050
computeExpirationForFiber,
5151
scheduleWork,
5252
} from './ReactFiberScheduler';
53+
import {checkForPendingContext} from './ReactFiberNewContext';
5354

5455
const fakeInternalInstance = {};
5556
const isArray = Array.isArray;
@@ -231,7 +232,7 @@ function checkShouldComponentUpdate(
231232
newProps,
232233
oldState,
233234
newState,
234-
newContext,
235+
nextLegacyContext,
235236
) {
236237
const instance = workInProgress.stateNode;
237238
const ctor = workInProgress.type;
@@ -240,7 +241,7 @@ function checkShouldComponentUpdate(
240241
const shouldUpdate = instance.shouldComponentUpdate(
241242
newProps,
242243
newState,
243-
newContext,
244+
nextLegacyContext,
244245
);
245246
stopPhaseTimer();
246247

@@ -616,15 +617,15 @@ function callComponentWillReceiveProps(
616617
workInProgress,
617618
instance,
618619
newProps,
619-
newContext,
620+
nextLegacyContext,
620621
) {
621622
const oldState = instance.state;
622623
startPhaseTimer(workInProgress, 'componentWillReceiveProps');
623624
if (typeof instance.componentWillReceiveProps === 'function') {
624-
instance.componentWillReceiveProps(newProps, newContext);
625+
instance.componentWillReceiveProps(newProps, nextLegacyContext);
625626
}
626627
if (typeof instance.UNSAFE_componentWillReceiveProps === 'function') {
627-
instance.UNSAFE_componentWillReceiveProps(newProps, newContext);
628+
instance.UNSAFE_componentWillReceiveProps(newProps, nextLegacyContext);
628629
}
629630
stopPhaseTimer();
630631

@@ -746,8 +747,16 @@ function resumeMountClassInstance(
746747
instance.props = oldProps;
747748

748749
const oldContext = instance.context;
749-
const newUnmaskedContext = getUnmaskedContext(workInProgress);
750-
const newContext = getMaskedContext(workInProgress, newUnmaskedContext);
750+
const nextLegacyUnmaskedContext = getUnmaskedContext(workInProgress);
751+
const nextLegacyContext = getMaskedContext(
752+
workInProgress,
753+
nextLegacyUnmaskedContext,
754+
);
755+
756+
const hasPendingNewContext = checkForPendingContext(
757+
workInProgress,
758+
renderExpirationTime,
759+
);
751760

752761
const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
753762
const hasNewLifecycles =
@@ -765,12 +774,12 @@ function resumeMountClassInstance(
765774
(typeof instance.UNSAFE_componentWillReceiveProps === 'function' ||
766775
typeof instance.componentWillReceiveProps === 'function')
767776
) {
768-
if (oldProps !== newProps || oldContext !== newContext) {
777+
if (oldProps !== newProps || oldContext !== nextLegacyContext) {
769778
callComponentWillReceiveProps(
770779
workInProgress,
771780
instance,
772781
newProps,
773-
newContext,
782+
nextLegacyContext,
774783
);
775784
}
776785
}
@@ -794,6 +803,7 @@ function resumeMountClassInstance(
794803
oldProps === newProps &&
795804
oldState === newState &&
796805
!hasContextChanged() &&
806+
!hasPendingNewContext &&
797807
!checkHasForceUpdateAfterProcessing()
798808
) {
799809
// If an update was already in progress, we should schedule an Update
@@ -815,13 +825,14 @@ function resumeMountClassInstance(
815825

816826
const shouldUpdate =
817827
checkHasForceUpdateAfterProcessing() ||
828+
hasPendingNewContext ||
818829
checkShouldComponentUpdate(
819830
workInProgress,
820831
oldProps,
821832
newProps,
822833
oldState,
823834
newState,
824-
newContext,
835+
nextLegacyContext,
825836
);
826837

827838
if (shouldUpdate) {
@@ -861,7 +872,7 @@ function resumeMountClassInstance(
861872
// if shouldComponentUpdate returns false.
862873
instance.props = newProps;
863874
instance.state = newState;
864-
instance.context = newContext;
875+
instance.context = nextLegacyContext;
865876

866877
return shouldUpdate;
867878
}
@@ -880,8 +891,16 @@ function updateClassInstance(
880891
instance.props = oldProps;
881892

882893
const oldContext = instance.context;
883-
const newUnmaskedContext = getUnmaskedContext(workInProgress);
884-
const newContext = getMaskedContext(workInProgress, newUnmaskedContext);
894+
const nextLegacyUnmaskedContext = getUnmaskedContext(workInProgress);
895+
const nextLegacyContext = getMaskedContext(
896+
workInProgress,
897+
nextLegacyUnmaskedContext,
898+
);
899+
900+
const hasPendingNewContext = checkForPendingContext(
901+
workInProgress,
902+
renderExpirationTime,
903+
);
885904

886905
const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
887906
const hasNewLifecycles =
@@ -899,12 +918,12 @@ function updateClassInstance(
899918
(typeof instance.UNSAFE_componentWillReceiveProps === 'function' ||
900919
typeof instance.componentWillReceiveProps === 'function')
901920
) {
902-
if (oldProps !== newProps || oldContext !== newContext) {
921+
if (oldProps !== newProps || oldContext !== nextLegacyContext) {
903922
callComponentWillReceiveProps(
904923
workInProgress,
905924
instance,
906925
newProps,
907-
newContext,
926+
nextLegacyContext,
908927
);
909928
}
910929
}
@@ -929,6 +948,7 @@ function updateClassInstance(
929948
oldProps === newProps &&
930949
oldState === newState &&
931950
!hasContextChanged() &&
951+
!hasPendingNewContext &&
932952
!checkHasForceUpdateAfterProcessing()
933953
) {
934954
// If an update was already in progress, we should schedule an Update
@@ -963,13 +983,14 @@ function updateClassInstance(
963983

964984
const shouldUpdate =
965985
checkHasForceUpdateAfterProcessing() ||
986+
hasPendingNewContext ||
966987
checkShouldComponentUpdate(
967988
workInProgress,
968989
oldProps,
969990
newProps,
970991
oldState,
971992
newState,
972-
newContext,
993+
nextLegacyContext,
973994
);
974995

975996
if (shouldUpdate) {
@@ -982,10 +1003,14 @@ function updateClassInstance(
9821003
) {
9831004
startPhaseTimer(workInProgress, 'componentWillUpdate');
9841005
if (typeof instance.componentWillUpdate === 'function') {
985-
instance.componentWillUpdate(newProps, newState, newContext);
1006+
instance.componentWillUpdate(newProps, newState, nextLegacyContext);
9861007
}
9871008
if (typeof instance.UNSAFE_componentWillUpdate === 'function') {
988-
instance.UNSAFE_componentWillUpdate(newProps, newState, newContext);
1009+
instance.UNSAFE_componentWillUpdate(
1010+
newProps,
1011+
newState,
1012+
nextLegacyContext,
1013+
);
9891014
}
9901015
stopPhaseTimer();
9911016
}
@@ -1025,7 +1050,7 @@ function updateClassInstance(
10251050
// if shouldComponentUpdate returns false.
10261051
instance.props = newProps;
10271052
instance.state = newState;
1028-
instance.context = newContext;
1053+
instance.context = nextLegacyContext;
10291054

10301055
return shouldUpdate;
10311056
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/**
2+
* Copyright (c) 2013-present, Facebook, Inc.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
*/
9+
10+
export {readContext} from './ReactFiberNewContext';

packages/react-reconciler/src/ReactFiberScheduler.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ import {
135135
commitAttachRef,
136136
commitDetachRef,
137137
} from './ReactFiberCommitWork';
138+
import * as dispatcher from './ReactFiberDispatcher';
138139

139140
export type Deadline = {
140141
timeRemaining: () => number,
@@ -985,6 +986,7 @@ function renderRoot(root: FiberRoot, isYieldy: boolean): void {
985986
'by a bug in React. Please file an issue.',
986987
);
987988
isWorking = true;
989+
ReactCurrentOwner.currentDispatcher = dispatcher;
988990

989991
const expirationTime = root.nextExpirationTimeToWorkOn;
990992

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

10721074
// We're done performing work. Time to clean up.
10731075
isWorking = false;
1076+
ReactCurrentOwner.currentDispatcher = null;
10741077

10751078
// Yield back to main thread.
10761079
if (didFatal) {

0 commit comments

Comments
 (0)