Clean up context access profiling experiment (#31806)
We introduced the `unstable_useContextWithBailout` API to run compiler based experiments. This API was designed to be an experiment proxy for alternative approaches which would be heavier to implement. The experiment turned out to be inconclusive. Since most of our performance critical usage is already optimized, we weren't able to find a clear win with this approach. Since we don't have further plans for this API, let's clean it up.
Jack Pope committed
Dec 16, 2024 at 12:32 UTC
909ed63e0adc162a95a4704d3ed07a956dcf9cd1
15 files changed
+16
-548
packages/react-debug-tools/src/ReactDebugHooks.js
+1
-29
@@ -20,7 +20,6 @@ import type {
20
Dependencies,
21
Fiber,
22
Dispatcher as DispatcherType,
23
- ContextDependencyWithSelect,
23
} from 'react-reconciler/src/ReactInternalTypes';
24
import type {TransitionStatus} from 'react-reconciler/src/ReactFiberConfig';
25
@@ -76,13 +75,6 @@ function getPrimitiveStackCache(): Map<string, Array<any>> {
75
try {
76
// Use all hooks here to add them to the hook log.
77
Dispatcher.useContext(({_currentValue: null}: any));
79
- if (typeof Dispatcher.unstable_useContextWithBailout === 'function') {
80
- // This type check is for Flow only.
81
- Dispatcher.unstable_useContextWithBailout(
82
- ({_currentValue: null}: any),
83
- null,
84
- );
85
- }
78
Dispatcher.useState(null);
79
Dispatcher.useReducer((s: mixed, a: mixed) => s, null);
80
Dispatcher.useRef(null);
@@ -150,10 +142,7 @@ function getPrimitiveStackCache(): Map<string, Array<any>> {
142
143
let currentFiber: null | Fiber = null;
144
let currentHook: null | Hook = null;
153
-let currentContextDependency:
154
- | null
155
- | ContextDependency<mixed>
156
- | ContextDependencyWithSelect<mixed> = null;
145
+let currentContextDependency: null | ContextDependency<mixed> = null;
146
147
function nextHook(): null | Hook {
148
const hook = currentHook;
@@ -274,22 +263,6 @@ function useContext<T>(context: ReactContext<T>): T {
263
return value;
264
}
265
277
-function unstable_useContextWithBailout<T>(
278
- context: ReactContext<T>,
279
- select: (T => Array<mixed>) | null,
280
-): T {
281
- const value = readContext(context);
282
- hookLog.push({
283
- displayName: context.displayName || null,
284
- primitive: 'ContextWithBailout',
285
- stackError: new Error(),
286
- value: value,
287
- debugInfo: null,
288
- dispatcherHookName: 'ContextWithBailout',
289
- });
290
- return value;
291
-}
292
-
266
function useState<S>(
267
initialState: (() => S) | S,
268
): [S, Dispatch<BasicStateAction<S>>] {
@@ -764,7 +737,6 @@ const Dispatcher: DispatcherType = {
737
useCacheRefresh,
738
useCallback,
739
useContext,
767
- unstable_useContextWithBailout,
740
useEffect,
741
useImperativeHandle,
742
useDebugValue,
packages/react-reconciler/src/ReactFiberHooks.js
+2
-113
@@ -39,12 +39,11 @@ import {
39
enableLazyContextPropagation,
40
enableTransitionTracing,
41
enableUseEffectEventHook,
42
+ enableUseResourceEffectHook,
43
enableLegacyCache,
44
debugRenderPhaseSideEffectsForStrictMode,
45
disableLegacyMode,
46
enableNoCloningMemoCache,
46
- enableContextProfiling,
47
- enableUseResourceEffectHook,
47
} from 'shared/ReactFeatureFlags';
48
import {
49
REACT_CONTEXT_TYPE,
@@ -78,11 +77,7 @@ import {
77
ContinuousEventPriority,
78
higherEventPriority,
79
} from './ReactEventPriorities';
81
-import {
82
- readContext,
83
- readContextAndCompare,
84
- checkIfContextChanged,
85
-} from './ReactFiberNewContext';
80
+import {readContext, checkIfContextChanged} from './ReactFiberNewContext';
81
import {HostRoot, CacheComponent, HostComponent} from './ReactWorkTags';
82
import {
83
LayoutStatic as LayoutStaticEffect,
@@ -1111,16 +1106,6 @@ function updateWorkInProgressHook(): Hook {
1106
return workInProgressHook;
1107
}
1108
1114
-function unstable_useContextWithBailout<T>(
1115
- context: ReactContext<T>,
1116
- select: (T => Array<mixed>) | null,
1117
-): T {
1118
- if (select === null) {
1119
- return readContext(context);
1120
- }
1121
- return readContextAndCompare(context, select);
1122
-}
1123
-
1109
function createFunctionComponentUpdateQueue(): FunctionComponentUpdateQueue {
1110
return {
1111
lastEffect: null,
@@ -3958,10 +3943,6 @@ if (enableUseEffectEventHook) {
3943
if (enableUseResourceEffectHook) {
3944
(ContextOnlyDispatcher: Dispatcher).useResourceEffect = throwInvalidHookError;
3945
}
3961
-if (enableContextProfiling) {
3962
- (ContextOnlyDispatcher: Dispatcher).unstable_useContextWithBailout =
3963
- throwInvalidHookError;
3964
-}
3946
3947
const HooksDispatcherOnMount: Dispatcher = {
3948
readContext,
@@ -3995,10 +3976,6 @@ if (enableUseEffectEventHook) {
3976
if (enableUseResourceEffectHook) {
3977
(HooksDispatcherOnMount: Dispatcher).useResourceEffect = mountResourceEffect;
3978
}
3998
-if (enableContextProfiling) {
3999
- (HooksDispatcherOnMount: Dispatcher).unstable_useContextWithBailout =
4000
- unstable_useContextWithBailout;
4001
-}
3979
3980
const HooksDispatcherOnUpdate: Dispatcher = {
3981
readContext,
@@ -4033,10 +4010,6 @@ if (enableUseResourceEffectHook) {
4010
(HooksDispatcherOnUpdate: Dispatcher).useResourceEffect =
4011
updateResourceEffect;
4012
}
4036
-if (enableContextProfiling) {
4037
- (HooksDispatcherOnUpdate: Dispatcher).unstable_useContextWithBailout =
4038
- unstable_useContextWithBailout;
4039
-}
4013
4014
const HooksDispatcherOnRerender: Dispatcher = {
4015
readContext,
@@ -4071,10 +4044,6 @@ if (enableUseResourceEffectHook) {
4044
(HooksDispatcherOnRerender: Dispatcher).useResourceEffect =
4045
updateResourceEffect;
4046
}
4074
-if (enableContextProfiling) {
4075
- (HooksDispatcherOnRerender: Dispatcher).unstable_useContextWithBailout =
4076
- unstable_useContextWithBailout;
4077
-}
4047
4048
let HooksDispatcherOnMountInDEV: Dispatcher | null = null;
4049
let HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher | null = null;
@@ -4296,17 +4265,6 @@ if (__DEV__) {
4265
);
4266
};
4267
}
4299
- if (enableContextProfiling) {
4300
- (HooksDispatcherOnMountInDEV: Dispatcher).unstable_useContextWithBailout =
4301
- function <T>(
4302
- context: ReactContext<T>,
4303
- select: (T => Array<mixed>) | null,
4304
- ): T {
4305
- currentHookNameInDev = 'useContext';
4306
- mountHookTypesDev();
4307
- return unstable_useContextWithBailout(context, select);
4308
- };
4309
- }
4268
4269
HooksDispatcherOnMountWithHookTypesInDEV = {
4270
readContext<T>(context: ReactContext<T>): T {
@@ -4494,17 +4452,6 @@ if (__DEV__) {
4452
);
4453
};
4454
}
4497
- if (enableContextProfiling) {
4498
- (HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).unstable_useContextWithBailout =
4499
- function <T>(
4500
- context: ReactContext<T>,
4501
- select: (T => Array<mixed>) | null,
4502
- ): T {
4503
- currentHookNameInDev = 'useContext';
4504
- updateHookTypesDev();
4505
- return unstable_useContextWithBailout(context, select);
4506
- };
4507
- }
4455
4456
HooksDispatcherOnUpdateInDEV = {
4457
readContext<T>(context: ReactContext<T>): T {
@@ -4692,17 +4639,6 @@ if (__DEV__) {
4639
);
4640
};
4641
}
4695
- if (enableContextProfiling) {
4696
- (HooksDispatcherOnUpdateInDEV: Dispatcher).unstable_useContextWithBailout =
4697
- function <T>(
4698
- context: ReactContext<T>,
4699
- select: (T => Array<mixed>) | null,
4700
- ): T {
4701
- currentHookNameInDev = 'useContext';
4702
- updateHookTypesDev();
4703
- return unstable_useContextWithBailout(context, select);
4704
- };
4705
- }
4642
4643
HooksDispatcherOnRerenderInDEV = {
4644
readContext<T>(context: ReactContext<T>): T {
@@ -4890,17 +4826,6 @@ if (__DEV__) {
4826
);
4827
};
4828
}
4893
- if (enableContextProfiling) {
4894
- (HooksDispatcherOnRerenderInDEV: Dispatcher).unstable_useContextWithBailout =
4895
- function <T>(
4896
- context: ReactContext<T>,
4897
- select: (T => Array<mixed>) | null,
4898
- ): T {
4899
- currentHookNameInDev = 'useContext';
4900
- updateHookTypesDev();
4901
- return unstable_useContextWithBailout(context, select);
4902
- };
4903
- }
4829
4830
InvalidNestedHooksDispatcherOnMountInDEV = {
4831
readContext<T>(context: ReactContext<T>): T {
@@ -5114,18 +5039,6 @@ if (__DEV__) {
5039
);
5040
};
5041
}
5117
- if (enableContextProfiling) {
5118
- (InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher).unstable_useContextWithBailout =
5119
- function <T>(
5120
- context: ReactContext<T>,
5121
- select: (T => Array<mixed>) | null,
5122
- ): T {
5123
- currentHookNameInDev = 'useContext';
5124
- warnInvalidHookAccess();
5125
- mountHookTypesDev();
5126
- return unstable_useContextWithBailout(context, select);
5127
- };
5128
- }
5042
5043
InvalidNestedHooksDispatcherOnUpdateInDEV = {
5044
readContext<T>(context: ReactContext<T>): T {
@@ -5339,18 +5252,6 @@ if (__DEV__) {
5252
);
5253
};
5254
}
5342
- if (enableContextProfiling) {
5343
- (InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).unstable_useContextWithBailout =
5344
- function <T>(
5345
- context: ReactContext<T>,
5346
- select: (T => Array<mixed>) | null,
5347
- ): T {
5348
- currentHookNameInDev = 'useContext';
5349
- warnInvalidHookAccess();
5350
- updateHookTypesDev();
5351
- return unstable_useContextWithBailout(context, select);
5352
- };
5353
- }
5255
5256
InvalidNestedHooksDispatcherOnRerenderInDEV = {
5257
readContext<T>(context: ReactContext<T>): T {
@@ -5564,16 +5465,4 @@ if (__DEV__) {
5465
);
5466
};
5467
}
5567
- if (enableContextProfiling) {
5568
- (InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).unstable_useContextWithBailout =
5569
- function <T>(
5570
- context: ReactContext<T>,
5571
- select: (T => Array<mixed>) | null,
5572
- ): T {
5573
- currentHookNameInDev = 'useContext';
5574
- warnInvalidHookAccess();
5575
- updateHookTypesDev();
5576
- return unstable_useContextWithBailout(context, select);
5577
- };
5578
- }
5468
}
packages/react-reconciler/src/ReactFiberNewContext.js
+6
-131
@@ -12,7 +12,6 @@ import type {
12
Fiber,
13
ContextDependency,
14
Dependencies,
15
- ContextDependencyWithSelect,
15
} from './ReactInternalTypes';
16
import type {StackCursor} from './ReactFiberStack';
17
import type {Lanes} from './ReactFiberLane';
@@ -48,8 +47,6 @@ import {
47
enableRenderableContext,
48
} from 'shared/ReactFeatureFlags';
49
import {getHostTransitionProvider} from './ReactFiberHostContext';
51
-import isArray from '../../shared/isArray';
52
-import {enableContextProfiling} from '../../shared/ReactFeatureFlags';
50
51
const valueCursor: StackCursor<mixed> = createCursor(null);
52
@@ -69,10 +66,7 @@ if (__DEV__) {
66
}
67
68
let currentlyRenderingFiber: Fiber | null = null;
72
-let lastContextDependency:
73
- | ContextDependency<mixed>
74
- | ContextDependencyWithSelect<mixed>
75
- | null = null;
69
+let lastContextDependency: ContextDependency<mixed> | null = null;
70
71
let isDisallowedContextReadInDEV: boolean = false;
72
@@ -401,23 +395,6 @@ function propagateContextChanges<T>(
395
const context: ReactContext<T> = contexts[i];
396
// Check if the context matches.
397
if (dependency.context === context) {
404
- if (enableContextProfiling) {
405
- const select = dependency.select;
406
- if (select != null && dependency.lastSelectedValue != null) {
407
- const newValue = isPrimaryRenderer
408
- ? dependency.context._currentValue
409
- : dependency.context._currentValue2;
410
- if (
411
- !checkIfSelectedContextValuesChanged(
412
- dependency.lastSelectedValue,
413
- select(newValue),
414
- )
415
- ) {
416
- // Compared value hasn't changed. Bail out early.
417
- continue findContext;
418
- }
419
- }
420
- }
398
// Match! Schedule an update on this fiber.
399
400
// In the lazy implementation, don't mark a dirty flag on the
@@ -657,29 +634,6 @@ function propagateParentContextChanges(
634
workInProgress.flags |= DidPropagateContext;
635
}
636
660
-function checkIfSelectedContextValuesChanged(
661
- oldComparedValue: Array<mixed>,
662
- newComparedValue: Array<mixed>,
663
-): boolean {
664
- // We have an implicit contract that compare functions must return arrays.
665
- // This allows us to compare multiple values in the same context access
666
- // since compiling to additional hook calls regresses perf.
667
- if (isArray(oldComparedValue) && isArray(newComparedValue)) {
668
- if (oldComparedValue.length !== newComparedValue.length) {
669
- return true;
670
- }
671
-
672
- for (let i = 0; i < oldComparedValue.length; i++) {
673
- if (!is(newComparedValue[i], oldComparedValue[i])) {
674
- return true;
675
- }
676
- }
677
- } else {
678
- throw new Error('Compared context values must be arrays');
679
- }
680
- return false;
681
-}
682
-
637
export function checkIfContextChanged(
638
currentDependencies: Dependencies,
639
): boolean {
@@ -698,23 +652,8 @@ export function checkIfContextChanged(
652
? context._currentValue
653
: context._currentValue2;
654
const oldValue = dependency.memoizedValue;
701
- if (
702
- enableContextProfiling &&
703
- dependency.select != null &&
704
- dependency.lastSelectedValue != null
705
- ) {
706
- if (
707
- checkIfSelectedContextValuesChanged(
708
- dependency.lastSelectedValue,
709
- dependency.select(newValue),
710
- )
711
- ) {
712
- return true;
713
- }
714
- } else {
715
- if (!is(newValue, oldValue)) {
716
- return true;
717
- }
655
+ if (!is(newValue, oldValue)) {
656
+ return true;
657
}
658
dependency = dependency.next;
659
}
@@ -747,21 +686,6 @@ export function prepareToReadContext(
686
}
687
}
688
750
-export function readContextAndCompare<C>(
751
- context: ReactContext<C>,
752
- select: C => Array<mixed>,
753
-): C {
754
- if (!(enableLazyContextPropagation && enableContextProfiling)) {
755
- throw new Error('Not implemented.');
756
- }
757
-
758
- return readContextForConsumer_withSelect(
759
- currentlyRenderingFiber,
760
- context,
761
- select,
762
- );
763
-}
764
-
689
export function readContext<T>(context: ReactContext<T>): T {
690
if (__DEV__) {
691
// This warning would fire if you read context inside a Hook like useMemo.
@@ -789,59 +713,10 @@ export function readContextDuringReconciliation<T>(
713
return readContextForConsumer(consumer, context);
714
}
715
792
-function readContextForConsumer_withSelect<C>(
793
- consumer: Fiber | null,
794
- context: ReactContext<C>,
795
- select: C => Array<mixed>,
796
-): C {
797
- const value = isPrimaryRenderer
798
- ? context._currentValue
799
- : context._currentValue2;
800
-
801
- const contextItem = {
802
- context: ((context: any): ReactContext<mixed>),
803
- memoizedValue: value,
804
- next: null,
805
- select: ((select: any): (context: mixed) => Array<mixed>),
806
- lastSelectedValue: select(value),
807
- };
808
-
809
- if (lastContextDependency === null) {
810
- if (consumer === null) {
811
- throw new Error(
812
- 'Context can only be read while React is rendering. ' +
813
- 'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
814
- 'In function components, you can read it directly in the function body, but not ' +
815
- 'inside Hooks like useReducer() or useMemo().',
816
- );
817
- }
818
-
819
- // This is the first dependency for this component. Create a new list.
820
- lastContextDependency = contextItem;
821
- consumer.dependencies = __DEV__
822
- ? {
823
- lanes: NoLanes,
824
- firstContext: contextItem,
825
- _debugThenableState: null,
826
- }
827
- : {
828
- lanes: NoLanes,
829
- firstContext: contextItem,
830
- };
831
- if (enableLazyContextPropagation) {
832
- consumer.flags |= NeedsPropagation;
833
- }
834
- } else {
835
- // Append a new context item.
836
- lastContextDependency = lastContextDependency.next = contextItem;
837
- }
838
- return value;
839
-}
840
-
841
-function readContextForConsumer<C>(
716
+function readContextForConsumer<T>(
717
consumer: Fiber | null,
843
- context: ReactContext<C>,
844
-): C {
718
+ context: ReactContext<T>,
719
+): T {
720
const value = isPrimaryRenderer
721
? context._currentValue
722
: context._currentValue2;
packages/react-reconciler/src/ReactInternalTypes.js
+7
-20
@@ -63,27 +63,18 @@ export type HookType =
63
| 'useFormState'
64
| 'useActionState';
65
66
-export type ContextDependency<C> = {
67
- context: ReactContext<C>,
68
- next: ContextDependency<mixed> | ContextDependencyWithSelect<mixed> | null,
69
- memoizedValue: C,
70
-};
71
-
72
-export type ContextDependencyWithSelect<C> = {
73
- context: ReactContext<C>,
74
- next: ContextDependency<mixed> | ContextDependencyWithSelect<mixed> | null,
75
- memoizedValue: C,
76
- select: C => Array<mixed>,
77
- lastSelectedValue: ?Array<mixed>,
66
+export type ContextDependency<T> = {
67
+ context: ReactContext<T>,
68
+ next: ContextDependency<mixed> | null,
69
+ memoizedValue: T,
70
+ ...
71
};
72
73
export type Dependencies = {
74
lanes: Lanes,
82
- firstContext:
83
- | ContextDependency<mixed>
84
- | ContextDependencyWithSelect<mixed>
85
- | null,
75
+ firstContext: ContextDependency<mixed> | null,
76
_debugThenableState?: null | ThenableState, // DEV-only
77
+ ...
78
};
79
80
export type MemoCache = {
@@ -401,10 +392,6 @@ export type Dispatcher = {
392
initialArg: I,
393
init?: (I) => S,
394
): [S, Dispatch<A>],
404
- unstable_useContextWithBailout?: <T>(
405
- context: ReactContext<T>,
406
- select: (T => Array<mixed>) | null,
407
- ) => T,
395
useContext<T>(context: ReactContext<T>): T,
396
useRef<T>(initialValue: T): {current: T},
397
useEffect(
packages/react-reconciler/src/__tests__/ReactContextWithBailout-test.js
deleted
-217
@@ -1,217 +0,0 @@
1
-let React;
2
-let ReactNoop;
3
-let Scheduler;
4
-let act;
5
-let assertLog;
6
-let useState;
7
-let useContext;
8
-let unstable_useContextWithBailout;
9
-
10
-describe('ReactContextWithBailout', () => {
11
- beforeEach(() => {
12
- jest.resetModules();
13
-
14
- React = require('react');
15
- ReactNoop = require('react-noop-renderer');
16
- Scheduler = require('scheduler');
17
- const testUtils = require('internal-test-utils');
18
- act = testUtils.act;
19
- assertLog = testUtils.assertLog;
20
- useState = React.useState;
21
- useContext = React.useContext;
22
- unstable_useContextWithBailout = React.unstable_useContextWithBailout;
23
- });
24
-
25
- function Text({text}) {
26
- Scheduler.log(text);
27
- return text;
28
- }
29
-
30
- // @gate enableLazyContextPropagation && enableContextProfiling
31
- test('unstable_useContextWithBailout basic usage', async () => {
32
- const Context = React.createContext();
33
-
34
- let setContext;
35
- function App() {
36
- const [context, _setContext] = useState({a: 'A0', b: 'B0', c: 'C0'});
37
- setContext = _setContext;
38
- return (
39
- <Context.Provider value={context}>
40
- <Indirection />
41
- </Context.Provider>
42
- );
43
- }
44
-
45
- // Intermediate parent that bails out. Children will only re-render when the
46
- // context changes.
47
- const Indirection = React.memo(() => {
48
- return (
49
- <>
50
- A: <A />, B: <B />, C: <C />, AB: <AB />
51
- </>
52
- );
53
- });
54
-
55
- function A() {
56
- const {a} = unstable_useContextWithBailout(Context, context => [
57
- context.a,
58
- ]);
59
- return <Text text={a} />;
60
- }
61
-
62
- function B() {
63
- const {b} = unstable_useContextWithBailout(Context, context => [
64
- context.b,
65
- ]);
66
- return <Text text={b} />;
67
- }
68
-
69
- function C() {
70
- const {c} = unstable_useContextWithBailout(Context, context => [
71
- context.c,
72
- ]);
73
- return <Text text={c} />;
74
- }
75
-
76
- function AB() {
77
- const {a, b} = unstable_useContextWithBailout(Context, context => [
78
- context.a,
79
- context.b,
80
- ]);
81
- return <Text text={a + b} />;
82
- }
83
-
84
- const root = ReactNoop.createRoot();
85
- await act(async () => {
86
- root.render(<App />);
87
- });
88
- assertLog(['A0', 'B0', 'C0', 'A0B0']);
89
- expect(root).toMatchRenderedOutput('A: A0, B: B0, C: C0, AB: A0B0');
90
-
91
- // Update a. Only the A and AB consumer should re-render.
92
- await act(async () => {
93
- setContext({a: 'A1', c: 'C0', b: 'B0'});
94
- });
95
- assertLog(['A1', 'A1B0']);
96
- expect(root).toMatchRenderedOutput('A: A1, B: B0, C: C0, AB: A1B0');
97
-
98
- // Update b. Only the B and AB consumer should re-render.
99
- await act(async () => {
100
- setContext({a: 'A1', b: 'B1', c: 'C0'});
101
- });
102
- assertLog(['B1', 'A1B1']);
103
- expect(root).toMatchRenderedOutput('A: A1, B: B1, C: C0, AB: A1B1');
104
-
105
- // Update c. Only the C consumer should re-render.
106
- await act(async () => {
107
- setContext({a: 'A1', b: 'B1', c: 'C1'});
108
- });
109
- assertLog(['C1']);
110
- expect(root).toMatchRenderedOutput('A: A1, B: B1, C: C1, AB: A1B1');
111
- });
112
-
113
- // @gate enableLazyContextPropagation && enableContextProfiling
114
- test('unstable_useContextWithBailout and useContext subscribing to same context in same component', async () => {
115
- const Context = React.createContext();
116
-
117
- let setContext;
118
- function App() {
119
- const [context, _setContext] = useState({a: 0, b: 0, unrelated: 0});
120
- setContext = _setContext;
121
- return (
122
- <Context.Provider value={context}>
123
- <Indirection />
124
- </Context.Provider>
125
- );
126
- }
127
-
128
- // Intermediate parent that bails out. Children will only re-render when the
129
- // context changes.
130
- const Indirection = React.memo(() => {
131
- return <Child />;
132
- });
133
-
134
- function Child() {
135
- const {a} = unstable_useContextWithBailout(Context, context => [
136
- context.a,
137
- ]);
138
- const context = useContext(Context);
139
- return <Text text={`A: ${a}, B: ${context.b}`} />;
140
- }
141
-
142
- const root = ReactNoop.createRoot();
143
- await act(async () => {
144
- root.render(<App />);
145
- });
146
- assertLog(['A: 0, B: 0']);
147
- expect(root).toMatchRenderedOutput('A: 0, B: 0');
148
-
149
- // Update an unrelated field that isn't used by the component. The context
150
- // attempts to bail out, but the normal context forces an update.
151
- await act(async () => {
152
- setContext({a: 0, b: 0, unrelated: 1});
153
- });
154
- assertLog(['A: 0, B: 0']);
155
- expect(root).toMatchRenderedOutput('A: 0, B: 0');
156
- });
157
-
158
- // @gate enableLazyContextPropagation && enableContextProfiling
159
- test('unstable_useContextWithBailout and useContext subscribing to different contexts in same component', async () => {
160
- const ContextA = React.createContext();
161
- const ContextB = React.createContext();
162
-
163
- let setContextA;
164
- let setContextB;
165
- function App() {
166
- const [a, _setContextA] = useState({a: 0, unrelated: 0});
167
- const [b, _setContextB] = useState(0);
168
- setContextA = _setContextA;
169
- setContextB = _setContextB;
170
- return (
171
- <ContextA.Provider value={a}>
172
- <ContextB.Provider value={b}>
173
- <Indirection />
174
- </ContextB.Provider>
175
- </ContextA.Provider>
176
- );
177
- }
178
-
179
- // Intermediate parent that bails out. Children will only re-render when the
180
- // context changes.
181
- const Indirection = React.memo(() => {
182
- return <Child />;
183
- });
184
-
185
- function Child() {
186
- const {a} = unstable_useContextWithBailout(ContextA, context => [
187
- context.a,
188
- ]);
189
- const b = useContext(ContextB);
190
- return <Text text={`A: ${a}, B: ${b}`} />;
191
- }
192
-
193
- const root = ReactNoop.createRoot();
194
- await act(async () => {
195
- root.render(<App />);
196
- });
197
- assertLog(['A: 0, B: 0']);
198
- expect(root).toMatchRenderedOutput('A: 0, B: 0');
199
-
200
- // Update a field in A that isn't part of the compared context. It should
201
- // bail out.
202
- await act(async () => {
203
- setContextA({a: 0, unrelated: 1});
204
- });
205
- assertLog([]);
206
- expect(root).toMatchRenderedOutput('A: 0, B: 0');
207
-
208
- // Now update the same a field again, but this time, also update a different
209
- // context in the same batch. The other context prevents a bail out.
210
- await act(async () => {
211
- setContextA({a: 0, unrelated: 1});
212
- setContextB(1);
213
- });
214
- assertLog(['A: 0, B: 1']);
215
- expect(root).toMatchRenderedOutput('A: 0, B: 1');
216
- });
217
-});
packages/react/index.fb.js
-1
@@ -42,7 +42,6 @@ export {
42
use,
43
useActionState,
44
useCallback,
45
- unstable_useContextWithBailout,
45
useContext,
46
useDebugValue,
47
useDeferredValue,
packages/react/src/ReactClient.js
-2
@@ -37,7 +37,6 @@ import {postpone} from './ReactPostpone';
37
import {
38
getCacheForType,
39
useCallback,
40
- unstable_useContextWithBailout,
40
useContext,
41
useEffect,
42
useEffectEvent,
@@ -86,7 +85,6 @@ export {
85
cache,
86
postpone as unstable_postpone,
87
useCallback,
89
- unstable_useContextWithBailout,
88
useContext,
89
useEffect,
90
useEffectEvent as experimental_useEffectEvent,
packages/react/src/ReactHooks.js
-25
@@ -19,10 +19,6 @@ import {REACT_CONSUMER_TYPE} from 'shared/ReactSymbols';
19
import ReactSharedInternals from 'shared/ReactSharedInternals';
20
21
import {enableUseResourceEffectHook} from 'shared/ReactFeatureFlags';
22
-import {
23
- enableContextProfiling,
24
- enableLazyContextPropagation,
25
-} from '../../shared/ReactFeatureFlags';
22
23
type BasicStateAction<S> = (S => S) | S;
24
type Dispatch<A> = A => void;
@@ -69,27 +65,6 @@ export function useContext<T>(Context: ReactContext<T>): T {
65
return dispatcher.useContext(Context);
66
}
67
72
-export function unstable_useContextWithBailout<T>(
73
- context: ReactContext<T>,
74
- select: (T => Array<mixed>) | null,
75
-): T {
76
- if (!(enableLazyContextPropagation && enableContextProfiling)) {
77
- throw new Error('Not implemented.');
78
- }
79
-
80
- const dispatcher = resolveDispatcher();
81
- if (__DEV__) {
82
- if (context.$$typeof === REACT_CONSUMER_TYPE) {
83
- console.error(
84
- 'Calling useContext(Context.Consumer) is not supported and will cause bugs. ' +
85
- 'Did you mean to call useContext(Context) instead?',
86
- );
87
- }
88
- }
89
- // $FlowFixMe[not-a-function] This is unstable, thus optional
90
- return dispatcher.unstable_useContextWithBailout(context, select);
91
-}
92
-
68
export function useState<S>(
69
initialState: (() => S) | S,
70
): [S, Dispatch<BasicStateAction<S>>] {
packages/shared/ReactFeatureFlags.js
-3
@@ -101,9 +101,6 @@ export const enableTransitionTracing = false;
101
102
export const enableLazyContextPropagation = true;
103
104
-// Expose unstable useContext for performance testing
105
-export const enableContextProfiling = false;
106
-
104
// FB-only usage. The new API has different semantics.
105
export const enableLegacyHidden = false;
106
packages/shared/forks/ReactFeatureFlags.native-fb.js
-1
@@ -53,7 +53,6 @@ export const enableFizzExternalRuntime = true;
53
export const enableGetInspectorDataForInstanceInProduction = true;
54
export const enableHalt = false;
55
export const enableInfiniteRenderLoopDetection = false;
56
-export const enableContextProfiling = false;
56
export const enableLazyContextPropagation = true;
57
export const enableLegacyCache = false;
58
export const enableLegacyFBSupport = false;
packages/shared/forks/ReactFeatureFlags.native-oss.js
-1
@@ -43,7 +43,6 @@ export const enableHalt = false;
43
export const enableHiddenSubtreeInsertionEffectCleanup = false;
44
export const enableInfiniteRenderLoopDetection = false;
45
export const enableLazyContextPropagation = true;
46
-export const enableContextProfiling = false;
46
export const enableLegacyCache = false;
47
export const enableLegacyFBSupport = false;
48
export const enableLegacyHidden = false;
packages/shared/forks/ReactFeatureFlags.test-renderer.js
-1
@@ -49,7 +49,6 @@ export const transitionLaneExpirationMs = 5000;
49
50
export const disableSchedulerTimeoutInWorkLoop = false;
51
export const enableLazyContextPropagation = true;
52
-export const enableContextProfiling = false;
52
export const enableLegacyHidden = false;
53
54
export const enableTransitionTracing = false;
packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
-1
@@ -51,7 +51,6 @@ export const transitionLaneExpirationMs = 5000;
51
52
export const disableSchedulerTimeoutInWorkLoop = false;
53
export const enableLazyContextPropagation = true;
54
-export const enableContextProfiling = false;
54
export const enableLegacyHidden = false;
55
56
export const enableTransitionTracing = false;
packages/shared/forks/ReactFeatureFlags.www.js
-2
@@ -79,8 +79,6 @@ export const enablePostpone = false;
79
80
export const enableHalt = false;
81
82
-export const enableContextProfiling = true;
83
-
82
// TODO: www currently relies on this feature. It's disabled in open source.
83
// Need to remove it.
84
export const disableCommentsAsDOMContainers = false;
scripts/error-codes/codes.json
-1
@@ -530,4 +530,3 @@
530
"542": "Suspense Exception: This is not a real error! It's an implementation detail of `useActionState` to interrupt the current render. You must either rethrow it immediately, or move the `useActionState` call outside of the `try/catch` block. Capturing without rethrowing will lead to unexpected behavior.\n\nTo handle async errors, wrap your component in an error boundary.",
531
"543": "Expected a ResourceEffectUpdate to be pushed together with ResourceEffectIdentity. This is a bug in React."
532
}
533
-