Skip to content

Commit b3885c2

Browse files
author
Jack Pope
committed
Fork readContextForConsumer
1 parent c6a3e4d commit b3885c2

File tree

5 files changed

+108
-36
lines changed

5 files changed

+108
-36
lines changed

packages/react-debug-tools/src/ReactDebugHooks.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import {
3737
REACT_CONTEXT_TYPE,
3838
} from 'shared/ReactSymbols';
3939
import hasOwnProperty from 'shared/hasOwnProperty';
40+
import type {ContextDependencyWithCompare} from '../../react-reconciler/src/ReactInternalTypes';
4041

4142
type CurrentDispatcherRef = typeof ReactSharedInternals;
4243

@@ -155,7 +156,10 @@ function getPrimitiveStackCache(): Map<string, Array<any>> {
155156

156157
let currentFiber: null | Fiber = null;
157158
let currentHook: null | Hook = null;
158-
let currentContextDependency: null | ContextDependency<mixed, mixed> = null;
159+
let currentContextDependency:
160+
| null
161+
| ContextDependency<mixed>
162+
| ContextDependencyWithCompare<mixed, mixed> = null;
159163

160164
function nextHook(): null | Hook {
161165
const hook = currentHook;

packages/react-reconciler/src/ReactFiberHooks.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1060,7 +1060,7 @@ function updateWorkInProgressHook(): Hook {
10601060

10611061
function unstable_useContextWithBailout<T>(
10621062
context: ReactContext<T>,
1063-
compare: void | (T => mixed),
1063+
compare: (T => mixed) | null,
10641064
): T {
10651065
return readContextAndCompare(context, compare);
10661066
}
@@ -4049,7 +4049,7 @@ if (__DEV__) {
40494049
}
40504050
if (enableContextProfiling) {
40514051
(HooksDispatcherOnMountInDEV: Dispatcher).unstable_useContextWithBailout =
4052-
function <T>(context: ReactContext<T>, compare: void | (T => mixed)): T {
4052+
function <T>(context: ReactContext<T>, compare: (T => mixed) | null): T {
40534053
currentHookNameInDev = 'useContext';
40544054
mountHookTypesDev();
40554055
return unstable_useContextWithBailout(context, compare);
@@ -4238,7 +4238,7 @@ if (__DEV__) {
42384238
}
42394239
if (enableContextProfiling) {
42404240
(HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).unstable_useContextWithBailout =
4241-
function <T>(context: ReactContext<T>, compare: void | (T => mixed)): T {
4241+
function <T>(context: ReactContext<T>, compare: (T => mixed) | null): T {
42424242
currentHookNameInDev = 'useContext';
42434243
updateHookTypesDev();
42444244
return unstable_useContextWithBailout(context, compare);
@@ -4426,7 +4426,7 @@ if (__DEV__) {
44264426
}
44274427
if (enableContextProfiling) {
44284428
(HooksDispatcherOnUpdateInDEV: Dispatcher).unstable_useContextWithBailout =
4429-
function <T>(context: ReactContext<T>, compare: void | (T => mixed)): T {
4429+
function <T>(context: ReactContext<T>, compare: (T => mixed) | null): T {
44304430
currentHookNameInDev = 'useContext';
44314431
updateHookTypesDev();
44324432
return unstable_useContextWithBailout(context, compare);
@@ -4614,7 +4614,7 @@ if (__DEV__) {
46144614
}
46154615
if (enableContextProfiling) {
46164616
(HooksDispatcherOnUpdateInDEV: Dispatcher).unstable_useContextWithBailout =
4617-
function <T>(context: ReactContext<T>, compare: void | (T => mixed)): T {
4617+
function <T>(context: ReactContext<T>, compare: (T => mixed) | null): T {
46184618
currentHookNameInDev = 'useContext';
46194619
updateHookTypesDev();
46204620
return unstable_useContextWithBailout(context, compare);
@@ -4828,7 +4828,7 @@ if (__DEV__) {
48284828
}
48294829
if (enableContextProfiling) {
48304830
(HooksDispatcherOnUpdateInDEV: Dispatcher).unstable_useContextWithBailout =
4831-
function <T>(context: ReactContext<T>, compare: void | (T => mixed)): T {
4831+
function <T>(context: ReactContext<T>, compare: (T => mixed) | null): T {
48324832
currentHookNameInDev = 'useContext';
48334833
warnInvalidHookAccess();
48344834
mountHookTypesDev();
@@ -5043,7 +5043,7 @@ if (__DEV__) {
50435043
}
50445044
if (enableContextProfiling) {
50455045
(InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).unstable_useContextWithBailout =
5046-
function <T>(context: ReactContext<T>, compare: void | (T => mixed)): T {
5046+
function <T>(context: ReactContext<T>, compare: (T => mixed) | null): T {
50475047
currentHookNameInDev = 'useContext';
50485048
warnInvalidHookAccess();
50495049
updateHookTypesDev();
@@ -5258,7 +5258,7 @@ if (__DEV__) {
52585258
}
52595259
if (enableContextProfiling) {
52605260
(InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).unstable_useContextWithBailout =
5261-
function <T>(context: ReactContext<T>, compare: void | (T => mixed)): T {
5261+
function <T>(context: ReactContext<T>, compare: (T => mixed) | null): T {
52625262
currentHookNameInDev = 'useContext';
52635263
warnInvalidHookAccess();
52645264
updateHookTypesDev();

packages/react-reconciler/src/ReactFiberNewContext.js

Lines changed: 74 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type {
1212
Fiber,
1313
ContextDependency,
1414
Dependencies,
15+
ContextDependencyWithCompare,
1516
} from './ReactInternalTypes';
1617
import type {StackCursor} from './ReactFiberStack';
1718
import type {Lanes} from './ReactFiberLane';
@@ -72,7 +73,10 @@ if (__DEV__) {
7273
}
7374

7475
let currentlyRenderingFiber: Fiber | null = null;
75-
let lastContextDependency: ContextDependency<mixed, mixed> | null = null;
76+
let lastContextDependency:
77+
| ContextDependency<mixed>
78+
| ContextDependencyWithCompare<mixed, mixed>
79+
| null = null;
7680
let lastFullyObservedContext: ReactContext<any> | null = null;
7781

7882
let isDisallowedContextReadInDEV: boolean = false;
@@ -403,19 +407,21 @@ function propagateContextChanges<T>(
403407
const context: ReactContext<T> = contexts[i];
404408
// Check if the context matches.
405409
if (dependency.context === context) {
406-
const compare = dependency.compare;
407-
if (enableContextProfiling && compare != null) {
408-
const newValue = isPrimaryRenderer
409-
? dependency.context._currentValue
410-
: dependency.context._currentValue2;
411-
if (
412-
!checkIfComparedContextValuesChanged(
413-
dependency.lastComparedValue,
414-
compare(newValue),
415-
)
416-
) {
417-
// Compared value hasn't changed. Bail out early.
418-
continue findContext;
410+
if (enableContextProfiling) {
411+
const compare = dependency.compare;
412+
if (compare != null) {
413+
const newValue = isPrimaryRenderer
414+
? dependency.context._currentValue
415+
: dependency.context._currentValue2;
416+
if (
417+
!checkIfComparedContextValuesChanged(
418+
dependency.lastComparedValue,
419+
compare(newValue),
420+
)
421+
) {
422+
// Compared value hasn't changed. Bail out early.
423+
continue findContext;
424+
}
419425
}
420426
}
421427
// Match! Schedule an update on this fiber.
@@ -746,13 +752,17 @@ export function prepareToReadContext(
746752

747753
export function readContextAndCompare<C>(
748754
context: ReactContext<C>,
749-
compare: void | (C => mixed),
755+
compare: (C => mixed) | null,
750756
): C {
751757
if (!enableLazyContextPropagation) {
752758
return readContext(context);
753759
}
754760

755-
return readContextForConsumer(currentlyRenderingFiber, context, compare);
761+
return readContextForConsumer_withCompare(
762+
currentlyRenderingFiber,
763+
context,
764+
compare,
765+
);
756766
}
757767

758768
export function readContext<T>(context: ReactContext<T>): T {
@@ -782,12 +792,12 @@ export function readContextDuringReconciliation<T>(
782792
return readContextForConsumer(consumer, context);
783793
}
784794

785-
type ContextCompare<C, S> = C => S;
795+
type ContextCompare<C, V> = C => V | null;
786796

787-
function readContextForConsumer<C, S>(
797+
function readContextForConsumer_withCompare<C, S>(
788798
consumer: Fiber | null,
789799
context: ReactContext<C>,
790-
compare?: void | (C => S),
800+
compare: (C => S) | null,
791801
): C {
792802
const value = isPrimaryRenderer
793803
? context._currentValue
@@ -800,7 +810,7 @@ function readContextForConsumer<C, S>(
800810
context: ((context: any): ReactContext<mixed>),
801811
memoizedValue: value,
802812
next: null,
803-
compare: ((compare: any): ContextCompare<mixed, mixed> | null),
813+
compare: compare ? ((compare: any): ContextCompare<mixed, mixed>) : null,
804814
lastComparedValue: compare != null ? compare(value) : null,
805815
};
806816

@@ -830,3 +840,47 @@ function readContextForConsumer<C, S>(
830840
}
831841
return value;
832842
}
843+
844+
function readContextForConsumer<C>(
845+
consumer: Fiber | null,
846+
context: ReactContext<C>,
847+
): C {
848+
const value = isPrimaryRenderer
849+
? context._currentValue
850+
: context._currentValue2;
851+
852+
if (lastFullyObservedContext === context) {
853+
// Nothing to do. We already observe everything in this context.
854+
} else {
855+
const contextItem = {
856+
context: ((context: any): ReactContext<mixed>),
857+
memoizedValue: value,
858+
next: null,
859+
};
860+
861+
if (lastContextDependency === null) {
862+
if (consumer === null) {
863+
throw new Error(
864+
'Context can only be read while React is rendering. ' +
865+
'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
866+
'In function components, you can read it directly in the function body, but not ' +
867+
'inside Hooks like useReducer() or useMemo().',
868+
);
869+
}
870+
871+
// This is the first dependency for this component. Create a new list.
872+
lastContextDependency = contextItem;
873+
consumer.dependencies = {
874+
lanes: NoLanes,
875+
firstContext: contextItem,
876+
};
877+
if (enableLazyContextPropagation) {
878+
consumer.flags |= NeedsPropagation;
879+
}
880+
} else {
881+
// Append a new context item.
882+
lastContextDependency = lastContextDependency.next = contextItem;
883+
}
884+
}
885+
return value;
886+
}

packages/react-reconciler/src/ReactInternalTypes.js

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,18 +61,32 @@ export type HookType =
6161
| 'useFormState'
6262
| 'useActionState';
6363

64-
export type ContextDependency<C, S> = {
64+
export type ContextDependency<C> = {
6565
context: ReactContext<C>,
66-
next: ContextDependency<mixed, mixed> | null,
66+
next:
67+
| ContextDependency<mixed>
68+
| ContextDependencyWithCompare<mixed, mixed>
69+
| null,
70+
memoizedValue: C,
71+
};
72+
73+
export type ContextDependencyWithCompare<C, S> = {
74+
context: ReactContext<C>,
75+
next:
76+
| ContextDependency<mixed>
77+
| ContextDependencyWithCompare<mixed, mixed>
78+
| null,
6779
memoizedValue: C,
6880
compare: (C => S) | null,
69-
lastComparedValue: S | null,
70-
...
81+
lastComparedValue?: S | null,
7182
};
7283

7384
export type Dependencies = {
7485
lanes: Lanes,
75-
firstContext: ContextDependency<mixed, mixed> | null,
86+
firstContext:
87+
| ContextDependency<mixed>
88+
| ContextDependencyWithCompare<mixed, mixed>
89+
| null,
7690
...
7791
};
7892

@@ -388,7 +402,7 @@ export type Dispatcher = {
388402
): [S, Dispatch<A>],
389403
unstable_useContextWithBailout?: <T>(
390404
context: ReactContext<T>,
391-
compare: void | (T => mixed),
405+
compare: (T => mixed) | null,
392406
) => T,
393407
useContext<T>(context: ReactContext<T>): T,
394408
useRef<T>(initialValue: T): {current: T},

packages/react/src/ReactHooks.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export function useContext<T>(Context: ReactContext<T>): T {
7171

7272
export function unstable_useContextWithBailout<T>(
7373
context: ReactContext<T>,
74-
compare: void | (T => mixed),
74+
compare: (T => mixed) | null,
7575
): T {
7676
if (!(enableLazyContextPropagation && enableContextProfiling)) {
7777
throw new Error('Not implemented.');

0 commit comments

Comments
 (0)