@samitouri / QOS-React-1 / commits / 909ed63e0a

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 -