Skip to content

Commit c0b8b58

Browse files
committed
Run selectors during propagation to bail out early
1 parent 85dc024 commit c0b8b58

File tree

8 files changed

+77
-54
lines changed

8 files changed

+77
-54
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,9 +114,9 @@ function readContext<T>(context: ReactContext<T>): T {
114114
return context._currentValue;
115115
}
116116

117-
function useContext<T, S>(
117+
function useContext<T>(
118118
context: ReactContext<T>,
119-
options?: {unstable_selector?: T => S},
119+
options?: {unstable_selector?: T => mixed},
120120
): T {
121121
hookLog.push({
122122
primitive: 'Context',

packages/react-dom/src/server/ReactPartialRendererHooks.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -235,9 +235,9 @@ function readContext<T>(context: ReactContext<T>): T {
235235
return context[threadID];
236236
}
237237

238-
function useContext<T, S>(
238+
function useContext<T>(
239239
context: ReactContext<T>,
240-
options?: {unstable_selector?: T => S},
240+
options?: {unstable_selector?: T => mixed},
241241
): T {
242242
if (__DEV__) {
243243
currentHookNameInDev = 'useContext';

packages/react-reconciler/src/ReactFiberHooks.new.js

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -680,9 +680,9 @@ function updateWorkInProgressHook(): Hook {
680680
return workInProgressHook;
681681
}
682682

683-
function useContext<T, S>(
683+
function useContext<T>(
684684
context: ReactContext<T>,
685-
options?: {unstable_selector?: T => S},
685+
options?: {unstable_selector?: T => mixed},
686686
): T {
687687
if (options !== undefined) {
688688
const selector = options.unstable_selector;
@@ -2216,9 +2216,9 @@ if (__DEV__) {
22162216
checkDepsAreArrayDev(deps);
22172217
return mountCallback(callback, deps);
22182218
},
2219-
useContext<T, S>(
2219+
useContext<T>(
22202220
context: ReactContext<T>,
2221-
options?: {unstable_selector?: T => S},
2221+
options?: {unstable_selector?: T => mixed},
22222222
): T {
22232223
currentHookNameInDev = 'useContext';
22242224
mountHookTypesDev();
@@ -2347,9 +2347,9 @@ if (__DEV__) {
23472347
updateHookTypesDev();
23482348
return mountCallback(callback, deps);
23492349
},
2350-
useContext<T, S>(
2350+
useContext<T>(
23512351
context: ReactContext<T>,
2352-
options?: {unstable_selector?: T => S},
2352+
options?: {unstable_selector?: T => mixed},
23532353
): T {
23542354
currentHookNameInDev = 'useContext';
23552355
updateHookTypesDev();
@@ -2474,9 +2474,9 @@ if (__DEV__) {
24742474
updateHookTypesDev();
24752475
return updateCallback(callback, deps);
24762476
},
2477-
useContext<T, S>(
2477+
useContext<T>(
24782478
context: ReactContext<T>,
2479-
options?: {unstable_selector?: T => S},
2479+
options?: {unstable_selector?: T => mixed},
24802480
): T {
24812481
currentHookNameInDev = 'useContext';
24822482
updateHookTypesDev();
@@ -2602,9 +2602,9 @@ if (__DEV__) {
26022602
updateHookTypesDev();
26032603
return updateCallback(callback, deps);
26042604
},
2605-
useContext<T, S>(
2605+
useContext<T>(
26062606
context: ReactContext<T>,
2607-
options?: {unstable_selector?: T => S},
2607+
options?: {unstable_selector?: T => mixed},
26082608
): T {
26092609
currentHookNameInDev = 'useContext';
26102610
updateHookTypesDev();
@@ -2731,9 +2731,9 @@ if (__DEV__) {
27312731
mountHookTypesDev();
27322732
return mountCallback(callback, deps);
27332733
},
2734-
useContext<T, S>(
2734+
useContext<T>(
27352735
context: ReactContext<T>,
2736-
options?: {unstable_selector?: T => S},
2736+
options?: {unstable_selector?: T => mixed},
27372737
): T {
27382738
currentHookNameInDev = 'useContext';
27392739
warnInvalidHookAccess();
@@ -2873,9 +2873,9 @@ if (__DEV__) {
28732873
updateHookTypesDev();
28742874
return updateCallback(callback, deps);
28752875
},
2876-
useContext<T, S>(
2876+
useContext<T>(
28772877
context: ReactContext<T>,
2878-
options?: {unstable_selector?: T => S},
2878+
options?: {unstable_selector?: T => mixed},
28792879
): T {
28802880
currentHookNameInDev = 'useContext';
28812881
warnInvalidHookAccess();
@@ -3016,9 +3016,9 @@ if (__DEV__) {
30163016
updateHookTypesDev();
30173017
return updateCallback(callback, deps);
30183018
},
3019-
useContext<T, S>(
3019+
useContext<T>(
30203020
context: ReactContext<T>,
3021-
options?: {unstable_selector?: T => S},
3021+
options?: {unstable_selector?: T => mixed},
30223022
): T {
30233023
currentHookNameInDev = 'useContext';
30243024
warnInvalidHookAccess();

packages/react-reconciler/src/ReactFiberHooks.old.js

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -680,9 +680,9 @@ function updateWorkInProgressHook(): Hook {
680680
return workInProgressHook;
681681
}
682682

683-
function useContext<T, S>(
683+
function useContext<T>(
684684
context: ReactContext<T>,
685-
options?: {unstable_selector?: T => S},
685+
options?: {unstable_selector?: T => mixed},
686686
): T {
687687
if (options !== undefined) {
688688
const selector = options.unstable_selector;
@@ -2216,9 +2216,9 @@ if (__DEV__) {
22162216
checkDepsAreArrayDev(deps);
22172217
return mountCallback(callback, deps);
22182218
},
2219-
useContext<T, S>(
2219+
useContext<T>(
22202220
context: ReactContext<T>,
2221-
options?: {unstable_selector?: T => S},
2221+
options?: {unstable_selector?: T => mixed},
22222222
): T {
22232223
currentHookNameInDev = 'useContext';
22242224
mountHookTypesDev();
@@ -2347,9 +2347,9 @@ if (__DEV__) {
23472347
updateHookTypesDev();
23482348
return mountCallback(callback, deps);
23492349
},
2350-
useContext<T, S>(
2350+
useContext<T>(
23512351
context: ReactContext<T>,
2352-
options?: {unstable_selector?: T => S},
2352+
options?: {unstable_selector?: T => mixed},
23532353
): T {
23542354
currentHookNameInDev = 'useContext';
23552355
updateHookTypesDev();
@@ -2474,9 +2474,9 @@ if (__DEV__) {
24742474
updateHookTypesDev();
24752475
return updateCallback(callback, deps);
24762476
},
2477-
useContext<T, S>(
2477+
useContext<T>(
24782478
context: ReactContext<T>,
2479-
options?: {unstable_selector?: T => S},
2479+
options?: {unstable_selector?: T => mixed},
24802480
): T {
24812481
currentHookNameInDev = 'useContext';
24822482
updateHookTypesDev();
@@ -2602,9 +2602,9 @@ if (__DEV__) {
26022602
updateHookTypesDev();
26032603
return updateCallback(callback, deps);
26042604
},
2605-
useContext<T, S>(
2605+
useContext<T>(
26062606
context: ReactContext<T>,
2607-
options?: {unstable_selector?: T => S},
2607+
options?: {unstable_selector?: T => mixed},
26082608
): T {
26092609
currentHookNameInDev = 'useContext';
26102610
updateHookTypesDev();
@@ -2731,9 +2731,9 @@ if (__DEV__) {
27312731
mountHookTypesDev();
27322732
return mountCallback(callback, deps);
27332733
},
2734-
useContext<T, S>(
2734+
useContext<T>(
27352735
context: ReactContext<T>,
2736-
options?: {unstable_selector?: T => S},
2736+
options?: {unstable_selector?: T => mixed},
27372737
): T {
27382738
currentHookNameInDev = 'useContext';
27392739
warnInvalidHookAccess();
@@ -2873,9 +2873,9 @@ if (__DEV__) {
28732873
updateHookTypesDev();
28742874
return updateCallback(callback, deps);
28752875
},
2876-
useContext<T, S>(
2876+
useContext<T>(
28772877
context: ReactContext<T>,
2878-
options?: {unstable_selector?: T => S},
2878+
options?: {unstable_selector?: T => mixed},
28792879
): T {
28802880
currentHookNameInDev = 'useContext';
28812881
warnInvalidHookAccess();
@@ -3016,9 +3016,9 @@ if (__DEV__) {
30163016
updateHookTypesDev();
30173017
return updateCallback(callback, deps);
30183018
},
3019-
useContext<T, S>(
3019+
useContext<T>(
30203020
context: ReactContext<T>,
3021-
options?: {unstable_selector?: T => S},
3021+
options?: {unstable_selector?: T => mixed},
30223022
): T {
30233023
currentHookNameInDev = 'useContext';
30243024
warnInvalidHookAccess();

packages/react-reconciler/src/ReactFiberNewContext.new.js

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,6 @@ function propagateContextChange_eager<T>(
212212
let dependency = list.firstContext;
213213
while (dependency !== null) {
214214
// Check if the context matches.
215-
// TODO: Compare selected values to bail out early.
216215
if (dependency.context === context) {
217216
// Match! Schedule an update on this fiber.
218217
if (fiber.tag === ClassComponent) {
@@ -348,8 +347,19 @@ function propagateContextChanges<T>(
348347
findContext: for (let i = 0; i < contexts.length; i++) {
349348
const context: ReactContext<T> = contexts[i];
350349
// Check if the context matches.
351-
// TODO: Compare selected values to bail out early.
352350
if (dependency.context === context) {
351+
const selector = dependency.selector;
352+
if (selector !== null) {
353+
const newValue = isPrimaryRenderer
354+
? context._currentValue
355+
: context._currentValue2;
356+
const newSelectedValue = selector(newValue);
357+
const oldSelectedValue = dependency.selectedValue;
358+
if (is(oldSelectedValue, selector(newSelectedValue))) {
359+
// Selected value hasn't changed. Bail out early.
360+
continue findContext;
361+
}
362+
}
353363
// Match! Schedule an update on this fiber.
354364

355365
// In the lazy implemenation, don't mark a dirty flag on the
@@ -571,10 +581,8 @@ export function checkIfContextChanged(currentDependencies: Dependencies) {
571581
const oldValue = dependency.memoizedValue;
572582
const selector = dependency.selector;
573583
if (selector !== null) {
574-
// TODO: Alternatively, we could store the selected value on the context.
575-
// However, we expect selectors to do nothing except access a subfield,
576-
// so this is probably fine, too.
577-
if (!is(selector(newValue), selector(oldValue))) {
584+
const oldSelectedValue = dependency.selectedValue;
585+
if (!is(selector(newValue), oldSelectedValue)) {
578586
return true;
579587
}
580588
} else {
@@ -659,8 +667,11 @@ function readContextImpl<C, S>(
659667
const contextItem = {
660668
context: ((context: any): ReactContext<mixed>),
661669
selector: ((selector: any): ContextSelector<mixed, mixed> | null),
662-
// TODO: Store selected value so we can compare to that during propagation
663670
memoizedValue: value,
671+
// TODO: If useContextSelector becomes a built-in API, then
672+
// readContextWithSelector should return the selected value so that we
673+
// don't call the selector twice. Will need to inline readContextImpl.
674+
selectedValue: selector !== null ? selector(value) : null,
664675
next: null,
665676
};
666677

packages/react-reconciler/src/ReactFiberNewContext.old.js

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,6 @@ function propagateContextChange_eager<T>(
212212
let dependency = list.firstContext;
213213
while (dependency !== null) {
214214
// Check if the context matches.
215-
// TODO: Compare selected values to bail out early.
216215
if (dependency.context === context) {
217216
// Match! Schedule an update on this fiber.
218217
if (fiber.tag === ClassComponent) {
@@ -348,8 +347,19 @@ function propagateContextChanges<T>(
348347
findContext: for (let i = 0; i < contexts.length; i++) {
349348
const context: ReactContext<T> = contexts[i];
350349
// Check if the context matches.
351-
// TODO: Compare selected values to bail out early.
352350
if (dependency.context === context) {
351+
const selector = dependency.selector;
352+
if (selector !== null) {
353+
const newValue = isPrimaryRenderer
354+
? context._currentValue
355+
: context._currentValue2;
356+
const newSelectedValue = selector(newValue);
357+
const oldSelectedValue = dependency.selectedValue;
358+
if (is(oldSelectedValue, selector(newSelectedValue))) {
359+
// Selected value hasn't changed. Bail out early.
360+
continue findContext;
361+
}
362+
}
353363
// Match! Schedule an update on this fiber.
354364

355365
// In the lazy implemenation, don't mark a dirty flag on the
@@ -571,10 +581,8 @@ export function checkIfContextChanged(currentDependencies: Dependencies) {
571581
const oldValue = dependency.memoizedValue;
572582
const selector = dependency.selector;
573583
if (selector !== null) {
574-
// TODO: Alternatively, we could store the selected value on the context.
575-
// However, we expect selectors to do nothing except access a subfield,
576-
// so this is probably fine, too.
577-
if (!is(selector(newValue), selector(oldValue))) {
584+
const oldSelectedValue = dependency.selectedValue;
585+
if (!is(selector(newValue), oldSelectedValue)) {
578586
return true;
579587
}
580588
} else {
@@ -659,8 +667,11 @@ function readContextImpl<C, S>(
659667
const contextItem = {
660668
context: ((context: any): ReactContext<mixed>),
661669
selector: ((selector: any): ContextSelector<mixed, mixed> | null),
662-
// TODO: Store selected value so we can compare to that during propagation
663670
memoizedValue: value,
671+
// TODO: If useContextSelector becomes a built-in API, then
672+
// readContextWithSelector should return the selected value so that we
673+
// don't call the selector twice. Will need to inline readContextImpl.
674+
selectedValue: selector !== null ? selector(value) : null,
664675
next: null,
665676
};
666677

packages/react-reconciler/src/ReactInternalTypes.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export type ContextDependency<C, S> = {
5151
selector: (C => S) | null,
5252
next: ContextDependency<mixed, mixed> | null,
5353
memoizedValue: C,
54+
selectedValue: S | null,
5455
...
5556
};
5657

@@ -282,9 +283,9 @@ export type Dispatcher = {|
282283
initialArg: I,
283284
init?: (I) => S,
284285
): [S, Dispatch<A>],
285-
useContext<T, S>(
286+
useContext<T>(
286287
context: ReactContext<T>,
287-
options?: {unstable_selector?: T => S},
288+
options?: {unstable_selector?: T => mixed},
288289
): T,
289290
useRef<T>(initialValue: T): {|current: T|},
290291
useEffect(

packages/react/src/ReactHooks.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ export function getCacheForType<T>(resourceType: () => T): T {
4747
return dispatcher.getCacheForType(resourceType);
4848
}
4949

50-
export function useContext<T, S>(
50+
export function useContext<T>(
5151
Context: ReactContext<T>,
52-
options?: {unstable_selector?: T => S},
52+
options?: {unstable_selector?: T => mixed},
5353
): T {
5454
const dispatcher = resolveDispatcher();
5555
if (__DEV__) {

0 commit comments

Comments
 (0)