Skip to content

Commit 4f7dbcf

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 d95b5f9 commit 4f7dbcf

File tree

8 files changed

+1252
-983
lines changed

8 files changed

+1252
-983
lines changed

packages/react-reconciler/src/ReactFiberBeginWork.js

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -237,19 +237,25 @@ function markRef(current: Fiber | null, workInProgress: Fiber) {
237237
}
238238
}
239239

240-
function updateFunctionalComponent(current, workInProgress) {
240+
function updateFunctionalComponent(
241+
current,
242+
workInProgress,
243+
renderExpirationTime,
244+
) {
241245
const fn = workInProgress.type;
242246
const nextProps = workInProgress.pendingProps;
243247

248+
const hasPendingContext = prepareToReadContext(
249+
workInProgress,
250+
renderExpirationTime,
251+
);
244252
if (hasLegacyContextChanged()) {
245253
// Normally we can bail out on props equality but if context has changed
246254
// we don't do the bailout and we have to reuse existing props instead.
247-
} else {
248-
if (workInProgress.memoizedProps === nextProps) {
249-
return bailoutOnAlreadyFinishedWork(current, workInProgress);
250-
}
255+
} else if (workInProgress.memoizedProps === nextProps && !hasPendingContext) {
251256
// TODO: consider bringing fn.shouldComponentUpdate() back.
252257
// It used to be here.
258+
return bailoutOnAlreadyFinishedWork(current, workInProgress);
253259
}
254260

255261
const unmaskedContext = getUnmaskedContext(workInProgress);
@@ -265,6 +271,7 @@ function updateFunctionalComponent(current, workInProgress) {
265271
} else {
266272
nextChildren = fn(nextProps, context);
267273
}
274+
268275
// React DevTools reads this flag.
269276
workInProgress.effectTag |= PerformedWork;
270277
reconcileChildren(current, workInProgress, nextChildren);
@@ -281,6 +288,11 @@ function updateClassComponent(
281288
// During mounting we don't know the child context yet as the instance doesn't exist.
282289
// We will invalidate the child context in finishClassComponent() right after rendering.
283290
const hasContext = pushLegacyContextProvider(workInProgress);
291+
const hasPendingNewContext = prepareToReadContext(
292+
workInProgress,
293+
renderExpirationTime,
294+
);
295+
284296
let shouldUpdate;
285297
if (current === null) {
286298
if (workInProgress.stateNode === null) {
@@ -297,13 +309,15 @@ function updateClassComponent(
297309
// In a resume, we'll already have an instance we can reuse.
298310
shouldUpdate = resumeMountClassInstance(
299311
workInProgress,
312+
hasPendingNewContext,
300313
renderExpirationTime,
301314
);
302315
}
303316
} else {
304317
shouldUpdate = updateClassInstance(
305318
current,
306319
workInProgress,
320+
hasPendingNewContext,
307321
renderExpirationTime,
308322
);
309323
}
@@ -580,6 +594,8 @@ function mountIndeterminateComponent(
580594
const unmaskedContext = getUnmaskedContext(workInProgress);
581595
const context = getMaskedContext(workInProgress, unmaskedContext);
582596

597+
prepareToReadContext(workInProgress, renderExpirationTime);
598+
583599
let value;
584600

585601
if (__DEV__) {
@@ -1082,7 +1098,11 @@ function beginWork(
10821098
renderExpirationTime,
10831099
);
10841100
case FunctionalComponent:
1085-
return updateFunctionalComponent(current, workInProgress);
1101+
return updateFunctionalComponent(
1102+
current,
1103+
workInProgress,
1104+
renderExpirationTime,
1105+
);
10861106
case ClassComponent:
10871107
return updateClassComponent(
10881108
current,

packages/react-reconciler/src/ReactFiberClassComponent.js

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ function checkShouldComponentUpdate(
231231
newProps,
232232
oldState,
233233
newState,
234-
newContext,
234+
nextLegacyContext,
235235
) {
236236
const instance = workInProgress.stateNode;
237237
const ctor = workInProgress.type;
@@ -240,7 +240,7 @@ function checkShouldComponentUpdate(
240240
const shouldUpdate = instance.shouldComponentUpdate(
241241
newProps,
242242
newState,
243-
newContext,
243+
nextLegacyContext,
244244
);
245245
stopPhaseTimer();
246246

@@ -616,15 +616,15 @@ function callComponentWillReceiveProps(
616616
workInProgress,
617617
instance,
618618
newProps,
619-
newContext,
619+
nextLegacyContext,
620620
) {
621621
const oldState = instance.state;
622622
startPhaseTimer(workInProgress, 'componentWillReceiveProps');
623623
if (typeof instance.componentWillReceiveProps === 'function') {
624-
instance.componentWillReceiveProps(newProps, newContext);
624+
instance.componentWillReceiveProps(newProps, nextLegacyContext);
625625
}
626626
if (typeof instance.UNSAFE_componentWillReceiveProps === 'function') {
627-
instance.UNSAFE_componentWillReceiveProps(newProps, newContext);
627+
instance.UNSAFE_componentWillReceiveProps(newProps, nextLegacyContext);
628628
}
629629
stopPhaseTimer();
630630

@@ -736,6 +736,7 @@ function mountClassInstance(
736736

737737
function resumeMountClassInstance(
738738
workInProgress: Fiber,
739+
hasPendingNewContext: boolean,
739740
renderExpirationTime: ExpirationTime,
740741
): boolean {
741742
const ctor = workInProgress.type;
@@ -746,8 +747,11 @@ 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+
);
751755

752756
const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
753757
const hasNewLifecycles =
@@ -765,12 +769,12 @@ function resumeMountClassInstance(
765769
(typeof instance.UNSAFE_componentWillReceiveProps === 'function' ||
766770
typeof instance.componentWillReceiveProps === 'function')
767771
) {
768-
if (oldProps !== newProps || oldContext !== newContext) {
772+
if (oldProps !== newProps || oldContext !== nextLegacyContext) {
769773
callComponentWillReceiveProps(
770774
workInProgress,
771775
instance,
772776
newProps,
773-
newContext,
777+
nextLegacyContext,
774778
);
775779
}
776780
}
@@ -794,6 +798,7 @@ function resumeMountClassInstance(
794798
oldProps === newProps &&
795799
oldState === newState &&
796800
!hasContextChanged() &&
801+
!hasPendingNewContext &&
797802
!checkHasForceUpdateAfterProcessing()
798803
) {
799804
// If an update was already in progress, we should schedule an Update
@@ -815,13 +820,14 @@ function resumeMountClassInstance(
815820

816821
const shouldUpdate =
817822
checkHasForceUpdateAfterProcessing() ||
823+
hasPendingNewContext ||
818824
checkShouldComponentUpdate(
819825
workInProgress,
820826
oldProps,
821827
newProps,
822828
oldState,
823829
newState,
824-
newContext,
830+
nextLegacyContext,
825831
);
826832

827833
if (shouldUpdate) {
@@ -861,7 +867,7 @@ function resumeMountClassInstance(
861867
// if shouldComponentUpdate returns false.
862868
instance.props = newProps;
863869
instance.state = newState;
864-
instance.context = newContext;
870+
instance.context = nextLegacyContext;
865871

866872
return shouldUpdate;
867873
}
@@ -870,6 +876,7 @@ function resumeMountClassInstance(
870876
function updateClassInstance(
871877
current: Fiber,
872878
workInProgress: Fiber,
879+
hasPendingNewContext: boolean,
873880
renderExpirationTime: ExpirationTime,
874881
): boolean {
875882
const ctor = workInProgress.type;
@@ -880,8 +887,11 @@ function updateClassInstance(
880887
instance.props = oldProps;
881888

882889
const oldContext = instance.context;
883-
const newUnmaskedContext = getUnmaskedContext(workInProgress);
884-
const newContext = getMaskedContext(workInProgress, newUnmaskedContext);
890+
const nextLegacyUnmaskedContext = getUnmaskedContext(workInProgress);
891+
const nextLegacyContext = getMaskedContext(
892+
workInProgress,
893+
nextLegacyUnmaskedContext,
894+
);
885895

886896
const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
887897
const hasNewLifecycles =
@@ -899,12 +909,12 @@ function updateClassInstance(
899909
(typeof instance.UNSAFE_componentWillReceiveProps === 'function' ||
900910
typeof instance.componentWillReceiveProps === 'function')
901911
) {
902-
if (oldProps !== newProps || oldContext !== newContext) {
912+
if (oldProps !== newProps || oldContext !== nextLegacyContext) {
903913
callComponentWillReceiveProps(
904914
workInProgress,
905915
instance,
906916
newProps,
907-
newContext,
917+
nextLegacyContext,
908918
);
909919
}
910920
}
@@ -929,6 +939,7 @@ function updateClassInstance(
929939
oldProps === newProps &&
930940
oldState === newState &&
931941
!hasContextChanged() &&
942+
!hasPendingNewContext &&
932943
!checkHasForceUpdateAfterProcessing()
933944
) {
934945
// If an update was already in progress, we should schedule an Update
@@ -963,13 +974,14 @@ function updateClassInstance(
963974

964975
const shouldUpdate =
965976
checkHasForceUpdateAfterProcessing() ||
977+
hasPendingNewContext ||
966978
checkShouldComponentUpdate(
967979
workInProgress,
968980
oldProps,
969981
newProps,
970982
oldState,
971983
newState,
972-
newContext,
984+
nextLegacyContext,
973985
);
974986

975987
if (shouldUpdate) {
@@ -982,10 +994,14 @@ function updateClassInstance(
982994
) {
983995
startPhaseTimer(workInProgress, 'componentWillUpdate');
984996
if (typeof instance.componentWillUpdate === 'function') {
985-
instance.componentWillUpdate(newProps, newState, newContext);
997+
instance.componentWillUpdate(newProps, newState, nextLegacyContext);
986998
}
987999
if (typeof instance.UNSAFE_componentWillUpdate === 'function') {
988-
instance.UNSAFE_componentWillUpdate(newProps, newState, newContext);
1000+
instance.UNSAFE_componentWillUpdate(
1001+
newProps,
1002+
newState,
1003+
nextLegacyContext,
1004+
);
9891005
}
9901006
stopPhaseTimer();
9911007
}
@@ -1025,7 +1041,7 @@ function updateClassInstance(
10251041
// if shouldComponentUpdate returns false.
10261042
instance.props = newProps;
10271043
instance.state = newState;
1028-
instance.context = newContext;
1044+
instance.context = nextLegacyContext;
10291045

10301046
return shouldUpdate;
10311047
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
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+
import {readContext} from './ReactFiberNewContext';
11+
12+
export const Dispatcher = {
13+
readContext,
14+
};

packages/react-reconciler/src/ReactFiberScheduler.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ import {
146146
commitAttachRef,
147147
commitDetachRef,
148148
} from './ReactFiberCommitWork';
149+
import {Dispatcher} from './ReactFiberDispatcher';
149150

150151
export type Deadline = {
151152
timeRemaining: () => number,
@@ -1011,6 +1012,7 @@ function renderRoot(
10111012
'by a bug in React. Please file an issue.',
10121013
);
10131014
isWorking = true;
1015+
ReactCurrentOwner.currentDispatcher = Dispatcher;
10141016

10151017
const expirationTime = root.nextExpirationTimeToWorkOn;
10161018

@@ -1101,6 +1103,7 @@ function renderRoot(
11011103

11021104
// We're done performing work. Time to clean up.
11031105
isWorking = false;
1106+
ReactCurrentOwner.currentDispatcher = null;
11041107

11051108
// Yield back to main thread.
11061109
if (didFatal) {

0 commit comments

Comments
 (0)