@samitouri / QOS-React-1 / commits / 378b305958

Warn about legacy context when legacy context is not disabled (#30297)

For environments that still have legacy contexts available, this adds a warning to make the remaining call sites easier to locate and encourage upgrades.

Jan Kassens committed Jul 10, 2024 at 11:53 UTC 378b305958eb7259cacfce8ad0e66eec07e07074
23 files changed +465 -150
packages/react-dom/src/__tests__/ReactDOMFiber-test.js
+8 -2
@@ -13,10 +13,12 @@ let React;
13 let ReactDOM;
14 let PropTypes;
15 let ReactDOMClient;
16 -let root;
16 let Scheduler;
17 +
18 let act;
19 +let assertConsoleErrorDev;
20 let assertLog;
21 +let root;
22
23 describe('ReactDOMFiber', () => {
24 let container;
@@ -29,7 +31,7 @@ describe('ReactDOMFiber', () => {
31 ReactDOMClient = require('react-dom/client');
32 Scheduler = require('scheduler');
33 act = require('internal-test-utils').act;
32 - assertLog = require('internal-test-utils').assertLog;
34 + ({assertConsoleErrorDev, assertLog} = require('internal-test-utils'));
35
36 container = document.createElement('div');
37 document.body.appendChild(container);
@@ -732,6 +734,10 @@ describe('ReactDOMFiber', () => {
734 await act(async () => {
735 root.render(<Parent />);
736 });
737 + assertConsoleErrorDev([
738 + 'Parent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
739 + 'Component uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
740 + ]);
741 expect(container.innerHTML).toBe('');
742 expect(portalContainer.innerHTML).toBe('<div>bar</div>');
743 });
packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
+14 -6
@@ -27,6 +27,8 @@ let ReactDOMFizzServer;
27 let ReactDOMFizzStatic;
28 let Suspense;
29 let SuspenseList;
30 +
31 +let assertConsoleErrorDev;
32 let useSyncExternalStore;
33 let useSyncExternalStoreWithSelector;
34 let use;
@@ -116,12 +118,14 @@ describe('ReactDOMFizzServer', () => {
118 useActionState = React.useActionState;
119 }
120
119 - const InternalTestUtils = require('internal-test-utils');
120 - waitForAll = InternalTestUtils.waitForAll;
121 - waitFor = InternalTestUtils.waitFor;
122 - waitForPaint = InternalTestUtils.waitForPaint;
123 - assertLog = InternalTestUtils.assertLog;
124 - clientAct = InternalTestUtils.act;
121 + ({
122 + assertConsoleErrorDev,
123 + assertLog,
124 + act: clientAct,
125 + waitFor,
126 + waitForAll,
127 + waitForPaint,
128 + } = require('internal-test-utils'));
129
130 if (gate(flags => flags.source)) {
131 // The `with-selector` module composes the main `use-sync-external-store`
@@ -1950,6 +1954,10 @@ describe('ReactDOMFizzServer', () => {
1954 );
1955 pipe(writable);
1956 });
1957 + assertConsoleErrorDev([
1958 + 'TestProvider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
1959 + 'TestConsumer uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
1960 + ]);
1961 expect(getVisibleChildren(container)).toEqual(
1962 <div>
1963 Loading: <b>A</b>
packages/react-dom/src/__tests__/ReactDOMLegacyFiber-test.js
+19 -3
@@ -786,7 +786,12 @@ describe('ReactDOMLegacyFiber', () => {
786 }
787 }
788
789 - ReactDOM.render(<Parent />, container);
789 + expect(() => {
790 + ReactDOM.render(<Parent />, container);
791 + }).toErrorDev([
792 + 'Parent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
793 + 'Component uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
794 + ]);
795 expect(container.innerHTML).toBe('');
796 expect(portalContainer.innerHTML).toBe('<div>bar</div>');
797 });
@@ -829,7 +834,13 @@ describe('ReactDOMLegacyFiber', () => {
834 }
835 }
836
832 - const instance = ReactDOM.render(<Parent />, container);
837 + let instance;
838 + expect(() => {
839 + instance = ReactDOM.render(<Parent />, container);
840 + }).toErrorDev([
841 + 'Parent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
842 + 'Component uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
843 + ]);
844 expect(portalContainer.innerHTML).toBe('<div>initial-initial</div>');
845 expect(container.innerHTML).toBe('');
846 instance.setState({bar: 'changed'});
@@ -871,7 +882,12 @@ describe('ReactDOMLegacyFiber', () => {
882 }
883 }
884
874 - ReactDOM.render(<Parent bar="initial" />, container);
885 + expect(() => {
886 + ReactDOM.render(<Parent bar="initial" />, container);
887 + }).toErrorDev([
888 + 'Parent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
889 + 'Component uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
890 + ]);
891 expect(portalContainer.innerHTML).toBe('<div>initial-initial</div>');
892 expect(container.innerHTML).toBe('');
893 ReactDOM.render(<Parent bar="changed" />, container);
packages/react-dom/src/__tests__/ReactDOMServerIntegrationLegacyContext-test.js
+12 -3
@@ -80,6 +80,7 @@ describe('ReactDOMServerIntegration', () => {
80 <PurpleContext>
81 <ClassChildWithContext />
82 </PurpleContext>,
83 + 2,
84 );
85 expect(e.textContent).toBe('purple');
86 });
@@ -94,6 +95,7 @@ describe('ReactDOMServerIntegration', () => {
95 <PurpleContext>
96 <FunctionChildWithContext />
97 </PurpleContext>,
98 + 2,
99 );
100 expect(e.textContent).toBe('purple');
101 });
@@ -110,6 +112,7 @@ describe('ReactDOMServerIntegration', () => {
112 <PurpleContext>
113 <ClassChildWithoutContext />
114 </PurpleContext>,
115 + 1,
116 );
117 expect(e.textContent).toBe('');
118 });
@@ -124,6 +127,7 @@ describe('ReactDOMServerIntegration', () => {
127 <PurpleContext>
128 <FunctionChildWithoutContext />
129 </PurpleContext>,
130 + 1,
131 );
132 expect(e.textContent).toBe('');
133 });
@@ -141,6 +145,7 @@ describe('ReactDOMServerIntegration', () => {
145 <PurpleContext>
146 <ClassChildWithWrongContext />
147 </PurpleContext>,
148 + 2,
149 );
150 expect(e.textContent).toBe('');
151 });
@@ -158,6 +163,7 @@ describe('ReactDOMServerIntegration', () => {
163 <PurpleContext>
164 <FunctionChildWithWrongContext />
165 </PurpleContext>,
166 + 2,
167 );
168 expect(e.textContent).toBe('');
169 });
@@ -174,6 +180,7 @@ describe('ReactDOMServerIntegration', () => {
180 <PurpleContext>
181 <Child />
182 </PurpleContext>,
183 + 2,
184 );
185 expect(e.textContent).toBe('purple');
186 });
@@ -190,6 +197,7 @@ describe('ReactDOMServerIntegration', () => {
197 <Grandchild />
198 </RedContext>
199 </PurpleContext>,
200 + 2,
201 );
202 expect(e.textContent).toBe('red');
203 });
@@ -228,7 +236,7 @@ describe('ReactDOMServerIntegration', () => {
236 text2: PropTypes.string,
237 };
238
231 - const e = await render(<Parent />);
239 + const e = await render(<Parent />, 3);
240 expect(e.querySelector('#first').textContent).toBe('purple');
241 expect(e.querySelector('#second').textContent).toBe('red');
242 });
@@ -254,7 +262,7 @@ describe('ReactDOMServerIntegration', () => {
262 };
263 Child.contextTypes = {text: PropTypes.string};
264
257 - const e = await render(<WillMountContext />);
265 + const e = await render(<WillMountContext />, 2);
266 expect(e.textContent).toBe('foo');
267 },
268 );
@@ -278,7 +286,8 @@ describe('ReactDOMServerIntegration', () => {
286 }
287 const e = await render(
288 <ForgetfulParent />,
281 - render === clientRenderOnBadMarkup ? 2 : 1,
289 + // Some warning is not de-duped and logged again on the client retry render.
290 + render === clientRenderOnBadMarkup ? 3 : 2,
291 );
292 expect(e.textContent).toBe('nope');
293 },
packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js
+5 -2
@@ -36,6 +36,7 @@ describe('ReactErrorBoundaries', () => {
36 let RetryErrorBoundary;
37 let Normal;
38 let assertLog;
39 + let assertConsoleErrorDev;
40
41 beforeEach(() => {
42 jest.useFakeTimers();
@@ -47,8 +48,7 @@ describe('ReactErrorBoundaries', () => {
48 act = require('internal-test-utils').act;
49 Scheduler = require('scheduler');
50
50 - const InternalTestUtils = require('internal-test-utils');
51 - assertLog = InternalTestUtils.assertLog;
51 + ({assertLog, assertConsoleErrorDev} = require('internal-test-utils'));
52
53 BrokenConstructor = class extends React.Component {
54 constructor(props) {
@@ -895,6 +895,9 @@ describe('ReactErrorBoundaries', () => {
895 </ErrorBoundary>,
896 );
897 });
898 + assertConsoleErrorDev([
899 + 'BrokenComponentWillMountWithContext uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
900 + ]);
901 expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
902 });
903
packages/react-dom/src/__tests__/ReactFunctionComponent-test.js
+11 -1
@@ -13,6 +13,7 @@ let PropTypes;
13 let React;
14 let ReactDOMClient;
15 let act;
16 +let assertConsoleErrorDev;
17
18 function FunctionComponent(props) {
19 return <div>{props.name}</div>;
@@ -24,7 +25,7 @@ describe('ReactFunctionComponent', () => {
25 PropTypes = require('prop-types');
26 React = require('react');
27 ReactDOMClient = require('react-dom/client');
27 - act = require('internal-test-utils').act;
28 + ({act, assertConsoleErrorDev} = require('internal-test-utils'));
29 });
30
31 it('should render stateless component', async () => {
@@ -109,6 +110,11 @@ describe('ReactFunctionComponent', () => {
110 root.render(<GrandParent test="test" />);
111 });
112
113 + assertConsoleErrorDev([
114 + 'GrandParent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
115 + 'Child uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
116 + ]);
117 +
118 expect(el.textContent).toBe('test');
119
120 await act(() => {
@@ -472,6 +478,10 @@ describe('ReactFunctionComponent', () => {
478 await act(() => {
479 root.render(<Parent />);
480 });
481 + assertConsoleErrorDev([
482 + 'Parent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
483 + 'Child uses the legacy contextTypes API which will be removed soon. Use React.createContext() with React.useContext() instead.',
484 + ]);
485 expect(el.textContent).toBe('en');
486 });
487
packages/react-dom/src/__tests__/ReactLegacyCompositeComponent-test.js
+68 -22
@@ -14,7 +14,9 @@ let ReactDOM;
14 let findDOMNode;
15 let ReactDOMClient;
16 let PropTypes;
17 +
18 let act;
19 +let assertConsoleErrorDev;
20
21 describe('ReactLegacyCompositeComponent', () => {
22 beforeEach(() => {
@@ -26,7 +28,7 @@ describe('ReactLegacyCompositeComponent', () => {
28 ReactDOM.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE
29 .findDOMNode;
30 PropTypes = require('prop-types');
29 - act = require('internal-test-utils').act;
31 + ({act, assertConsoleErrorDev} = require('internal-test-utils'));
32 });
33
34 // @gate !disableLegacyMode
@@ -119,6 +121,10 @@ describe('ReactLegacyCompositeComponent', () => {
121 await act(() => {
122 root.render(<Parent ref={current => (component = current)} />);
123 });
124 + assertConsoleErrorDev([
125 + 'Child uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
126 + 'Grandchild uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
127 + ]);
128 expect(findDOMNode(component).innerHTML).toBe('bar');
129 });
130
@@ -183,6 +189,11 @@ describe('ReactLegacyCompositeComponent', () => {
189 expect(parentInstance.state.flag).toBe(false);
190 expect(childInstance.context).toEqual({foo: 'bar', flag: false});
191
192 + assertConsoleErrorDev([
193 + 'Parent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
194 + 'Child uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
195 + ]);
196 +
197 await act(() => {
198 parentInstance.setState({flag: true});
199 });
@@ -242,6 +253,11 @@ describe('ReactLegacyCompositeComponent', () => {
253 root.render(<Wrapper ref={current => (wrapper = current)} />);
254 });
255
256 + assertConsoleErrorDev([
257 + 'Parent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
258 + 'Child uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
259 + ]);
260 +
261 expect(wrapper.parentRef.current.state.flag).toEqual(true);
262 expect(wrapper.childRef.current.context).toEqual({flag: true});
263
@@ -317,6 +333,13 @@ describe('ReactLegacyCompositeComponent', () => {
333 root.render(<Parent />);
334 });
335
336 + assertConsoleErrorDev([
337 + 'Parent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
338 + 'Child uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
339 + 'Child uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
340 + 'Grandchild uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
341 + ]);
342 +
343 expect(childInstance.context).toEqual({foo: 'bar', depth: 0});
344 expect(grandchildInstance.context).toEqual({foo: 'bar', depth: 1});
345 });
@@ -369,6 +392,9 @@ describe('ReactLegacyCompositeComponent', () => {
392 await act(() => {
393 root.render(<Parent ref={current => (parentInstance = current)} />);
394 });
395 + assertConsoleErrorDev([
396 + 'Parent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
397 + ]);
398
399 expect(childInstance).toBeNull();
400
@@ -376,6 +402,10 @@ describe('ReactLegacyCompositeComponent', () => {
402 await act(() => {
403 parentInstance.setState({flag: true});
404 });
405 + assertConsoleErrorDev([
406 + 'Child uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
407 + ]);
408 +
409 expect(parentInstance.state.flag).toBe(true);
410
411 expect(childInstance.context).toEqual({foo: 'bar', depth: 0});
@@ -435,7 +465,12 @@ describe('ReactLegacyCompositeComponent', () => {
465 }
466
467 const div = document.createElement('div');
438 - ReactDOM.render(<Parent cntxt="noise" />, div);
468 + expect(() => {
469 + ReactDOM.render(<Parent cntxt="noise" />, div);
470 + }).toErrorDev([
471 + 'Parent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
472 + 'Leaf uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
473 + ]);
474 expect(div.children[0].innerHTML).toBe('noise');
475 div.children[0].innerHTML = 'aliens';
476 div.children[0].id = 'aliens';
@@ -537,20 +572,26 @@ describe('ReactLegacyCompositeComponent', () => {
572 const div = document.createElement('div');
573
574 let parentInstance = null;
540 - ReactDOM.render(
541 - <Parent ref={inst => (parentInstance = inst)}>
542 - <ChildWithoutContext>
543 - A1
544 - <GrandChild>A2</GrandChild>
545 - </ChildWithoutContext>
546 -
547 - <ChildWithContext>
548 - B1
549 - <GrandChild>B2</GrandChild>
550 - </ChildWithContext>
551 - </Parent>,
552 - div,
553 - );
575 + expect(() => {
576 + ReactDOM.render(
577 + <Parent ref={inst => (parentInstance = inst)}>
578 + <ChildWithoutContext>
579 + A1
580 + <GrandChild>A2</GrandChild>
581 + </ChildWithoutContext>
582 +
583 + <ChildWithContext>
584 + B1
585 + <GrandChild>B2</GrandChild>
586 + </ChildWithContext>
587 + </Parent>,
588 + div,
589 + );
590 + }).toErrorDev([
591 + 'Parent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
592 + 'GrandChild uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
593 + 'ChildWithContext uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
594 + ]);
595
596 parentInstance.setState({
597 foo: 'def',
@@ -733,12 +774,17 @@ describe('ReactLegacyCompositeComponent', () => {
774 }
775
776 const div = document.createElement('div');
736 - ReactDOM.render(
737 - <Parent>
738 - <Component />
739 - </Parent>,
740 - div,
741 - );
777 + expect(() => {
778 + ReactDOM.render(
779 + <Parent>
780 + <Component />
781 + </Parent>,
782 + div,
783 + );
784 + }).toErrorDev([
785 + 'Parent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
786 + 'Component uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
787 + ]);
788 });
789
790 it('should replace state in legacy mode', async () => {
packages/react-dom/src/__tests__/ReactLegacyErrorBoundaries-test.internal.js
+3
@@ -849,6 +849,9 @@ describe('ReactLegacyErrorBoundaries', () => {
849 </ErrorBoundary>,
850 container,
851 );
852 + assertConsoleErrorDev([
853 + 'BrokenComponentWillMountWithContext uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
854 + ]);
855 expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
856 });
857
packages/react-dom/src/__tests__/ReactServerRendering-test.js
+11 -5
@@ -371,11 +371,17 @@ describe('ReactDOMServer', () => {
371 text: PropTypes.string,
372 };
373
374 - const markup = ReactDOMServer.renderToStaticMarkup(
375 - <ContextProvider>
376 - <Component />
377 - </ContextProvider>,
378 - );
374 + let markup;
375 + expect(() => {
376 + markup = ReactDOMServer.renderToStaticMarkup(
377 + <ContextProvider>
378 + <Component />
379 + </ContextProvider>,
380 + );
381 + }).toErrorDev([
382 + 'ContextProvider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
383 + 'Component uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
384 + ]);
385 expect(markup).toContain('hello, world');
386 });
387
packages/react-native-renderer/src/__tests__/ReactNativeEvents-test.internal.js
+24 -20
@@ -227,27 +227,31 @@ test('handles events on text nodes', () => {
227 }
228
229 const log = [];
230 - ReactNative.render(
231 - <ContextHack>
232 - <Text>
233 - <Text
234 - onTouchEnd={() => log.push('string touchend')}
235 - onTouchEndCapture={() => log.push('string touchend capture')}
236 - onTouchStart={() => log.push('string touchstart')}
237 - onTouchStartCapture={() => log.push('string touchstart capture')}>
238 - Text Content
239 - </Text>
240 - <Text
241 - onTouchEnd={() => log.push('number touchend')}
242 - onTouchEndCapture={() => log.push('number touchend capture')}
243 - onTouchStart={() => log.push('number touchstart')}
244 - onTouchStartCapture={() => log.push('number touchstart capture')}>
245 - {123}
230 + expect(() => {
231 + ReactNative.render(
232 + <ContextHack>
233 + <Text>
234 + <Text
235 + onTouchEnd={() => log.push('string touchend')}
236 + onTouchEndCapture={() => log.push('string touchend capture')}
237 + onTouchStart={() => log.push('string touchstart')}
238 + onTouchStartCapture={() => log.push('string touchstart capture')}>
239 + Text Content
240 + </Text>
241 + <Text
242 + onTouchEnd={() => log.push('number touchend')}
243 + onTouchEndCapture={() => log.push('number touchend capture')}
244 + onTouchStart={() => log.push('number touchstart')}
245 + onTouchStartCapture={() => log.push('number touchstart capture')}>
246 + {123}
247 + </Text>
248 </Text>
247 - </Text>
248 - </ContextHack>,
249 - 1,
250 - );
249 + </ContextHack>,
250 + 1,
251 + );
252 + }).toErrorDev([
253 + 'ContextHack uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
254 + ]);
255
256 expect(UIManager.createView).toHaveBeenCalledTimes(5);
257
packages/react-reconciler/src/ReactFiberBeginWork.js
+29 -14
@@ -316,6 +316,7 @@ let didReceiveUpdate: boolean = false;
316
317 let didWarnAboutBadClass;
318 let didWarnAboutContextTypeOnFunctionComponent;
319 +let didWarnAboutContextTypes;
320 let didWarnAboutGetDerivedStateOnFunctionComponent;
321 let didWarnAboutFunctionRefs;
322 export let didWarnAboutReassigningProps: boolean;
@@ -326,6 +327,7 @@ let didWarnAboutDefaultPropsOnFunctionComponent;
327 if (__DEV__) {
328 didWarnAboutBadClass = ({}: {[string]: boolean});
329 didWarnAboutContextTypeOnFunctionComponent = ({}: {[string]: boolean});
330 + didWarnAboutContextTypes = ({}: {[string]: boolean});
331 didWarnAboutGetDerivedStateOnFunctionComponent = ({}: {[string]: boolean});
332 didWarnAboutFunctionRefs = ({}: {[string]: boolean});
333 didWarnAboutReassigningProps = false;
@@ -1130,12 +1132,27 @@ function updateFunctionComponent(
1132 // in updateFuntionComponent but only on mount
1133 validateFunctionComponentInDev(workInProgress, workInProgress.type);
1134
1133 - if (disableLegacyContext && Component.contextTypes) {
1134 - console.error(
1135 - '%s uses the legacy contextTypes API which was removed in React 19. ' +
1136 - 'Use React.createContext() with React.useContext() instead.',
1137 - getComponentNameFromType(Component) || 'Unknown',
1138 - );
1135 + if (Component.contextTypes) {
1136 + const componentName = getComponentNameFromType(Component) || 'Unknown';
1137 +
1138 + if (!didWarnAboutContextTypes[componentName]) {
1139 + didWarnAboutContextTypes[componentName] = true;
1140 + if (disableLegacyContext) {
1141 + console.error(
1142 + '%s uses the legacy contextTypes API which was removed in React 19. ' +
1143 + 'Use React.createContext() with React.useContext() instead. ' +
1144 + '(https://react.dev/link/legacy-context)',
1145 + componentName,
1146 + );
1147 + } else {
1148 + console.error(
1149 + '%s uses the legacy contextTypes API which will be removed soon. ' +
1150 + 'Use React.createContext() with React.useContext() instead. ' +
1151 + '(https://react.dev/link/legacy-context)',
1152 + componentName,
1153 + );
1154 + }
1155 + }
1156 }
1157 }
1158 }
@@ -1923,14 +1940,12 @@ function mountIncompleteClassComponent(
1940
1941 function validateFunctionComponentInDev(workInProgress: Fiber, Component: any) {
1942 if (__DEV__) {
1926 - if (Component) {
1927 - if (Component.childContextTypes) {
1928 - console.error(
1929 - 'childContextTypes cannot be defined on a function component.\n' +
1930 - ' %s.childContextTypes = ...',
1931 - Component.displayName || Component.name || 'Component',
1932 - );
1933 - }
1943 + if (Component && Component.childContextTypes) {
1944 + console.error(
1945 + 'childContextTypes cannot be defined on a function component.\n' +
1946 + ' %s.childContextTypes = ...',
1947 + Component.displayName || Component.name || 'Component',
1948 + );
1949 }
1950 if (!enableRefAsProp && workInProgress.ref !== null) {
1951 let info = '';
packages/react-reconciler/src/ReactFiberClassComponent.js
+20 -2
@@ -393,7 +393,7 @@ function checkClassInstance(workInProgress: Fiber, ctor: any, newProps: any) {
393 didWarnAboutChildContextTypes.add(ctor);
394 console.error(
395 '%s uses the legacy childContextTypes API which was removed in React 19. ' +
396 - 'Use React.createContext() instead.',
396 + 'Use React.createContext() instead. (https://react.dev/link/legacy-context)',
397 name,
398 );
399 }
@@ -401,7 +401,8 @@ function checkClassInstance(workInProgress: Fiber, ctor: any, newProps: any) {
401 didWarnAboutContextTypes.add(ctor);
402 console.error(
403 '%s uses the legacy contextTypes API which was removed in React 19. ' +
404 - 'Use React.createContext() with static contextType instead.',
404 + 'Use React.createContext() with static contextType instead. ' +
405 + '(https://react.dev/link/legacy-context)',
406 name,
407 );
408 }
@@ -426,6 +427,23 @@ function checkClassInstance(workInProgress: Fiber, ctor: any, newProps: any) {
427 name,
428 );
429 }
430 + if (ctor.childContextTypes && !didWarnAboutChildContextTypes.has(ctor)) {
431 + didWarnAboutChildContextTypes.add(ctor);
432 + console.error(
433 + '%s uses the legacy childContextTypes API which will soon be removed. ' +
434 + 'Use React.createContext() instead. (https://react.dev/link/legacy-context)',
435 + name,
436 + );
437 + }
438 + if (ctor.contextTypes && !didWarnAboutContextTypes.has(ctor)) {
439 + didWarnAboutContextTypes.add(ctor);
440 + console.error(
441 + '%s uses the legacy contextTypes API which will soon be removed. ' +
442 + 'Use React.createContext() with static contextType instead. ' +
443 + '(https://react.dev/link/legacy-context)',
444 + name,
445 + );
446 + }
447 }
448
449 if (typeof instance.componentShouldUpdate === 'function') {
packages/react-reconciler/src/__tests__/ReactIncremental-test.js
+68 -6
@@ -14,6 +14,8 @@ let React;
14 let ReactNoop;
15 let Scheduler;
16 let PropTypes;
17 +
18 +let assertConsoleErrorDev;
19 let waitForAll;
20 let waitFor;
21 let waitForThrow;
@@ -27,11 +29,13 @@ describe('ReactIncremental', () => {
29 Scheduler = require('scheduler');
30 PropTypes = require('prop-types');
31
30 - const InternalTestUtils = require('internal-test-utils');
31 - waitForAll = InternalTestUtils.waitForAll;
32 - waitFor = InternalTestUtils.waitFor;
33 - waitForThrow = InternalTestUtils.waitForThrow;
34 - assertLog = InternalTestUtils.assertLog;
32 + ({
33 + assertConsoleErrorDev,
34 + waitForAll,
35 + waitFor,
36 + waitForThrow,
37 + assertLog,
38 + } = require('internal-test-utils'));
39 });
40
41 // Note: This is based on a similar component we use in www. We can delete
@@ -1793,6 +1797,11 @@ describe('ReactIncremental', () => {
1797 'ShowLocale {"locale":"fr"}',
1798 'ShowBoth {"locale":"fr"}',
1799 ]);
1800 + assertConsoleErrorDev([
1801 + 'Intl uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
1802 + 'ShowLocale uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
1803 + 'ShowBoth uses the legacy contextTypes API which will be removed soon. Use React.createContext() with React.useContext() instead.',
1804 + ]);
1805
1806 ReactNoop.render(
1807 <Intl locale="de">
@@ -1843,6 +1852,10 @@ describe('ReactIncremental', () => {
1852 'ShowBoth {"locale":"en","route":"/about"}',
1853 'ShowBoth {"locale":"en"}',
1854 ]);
1855 + assertConsoleErrorDev([
1856 + 'Router uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
1857 + 'ShowRoute uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
1858 + ]);
1859 });
1860
1861 // @gate !disableLegacyContext
@@ -1876,6 +1889,10 @@ describe('ReactIncremental', () => {
1889 'Recurse {"n":1}',
1890 'Recurse {"n":0}',
1891 ]);
1892 + assertConsoleErrorDev([
1893 + 'Recurse uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
1894 + 'Recurse uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
1895 + ]);
1896 });
1897
1898 // @gate enableLegacyHidden && !disableLegacyContext
@@ -1925,6 +1942,10 @@ describe('ReactIncremental', () => {
1942 'ShowLocale {"locale":"fr"}',
1943 'ShowLocale {"locale":"fr"}',
1944 ]);
1945 + assertConsoleErrorDev([
1946 + 'Intl uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
1947 + 'ShowLocale uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
1948 + ]);
1949
1950 await waitForAll([
1951 'ShowLocale {"locale":"fr"}',
@@ -2012,6 +2033,11 @@ describe('ReactIncremental', () => {
2033 'ShowLocaleClass:read {"locale":"fr"}',
2034 'ShowLocaleFn:read {"locale":"fr"}',
2035 ]);
2036 + assertConsoleErrorDev([
2037 + 'Intl uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
2038 + 'ShowLocaleClass uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
2039 + 'ShowLocaleFn uses the legacy contextTypes API which will be removed soon. Use React.createContext() with React.useContext() instead.',
2040 + ]);
2041
2042 statefulInst.setState({x: 1});
2043 await waitForAll([]);
@@ -2098,6 +2124,12 @@ describe('ReactIncremental', () => {
2124 'ShowLocaleFn:read {"locale":"fr"}',
2125 ]);
2126
2127 + assertConsoleErrorDev([
2128 + 'Intl uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
2129 + 'ShowLocaleClass uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
2130 + 'ShowLocaleFn uses the legacy contextTypes API which will be removed soon. Use React.createContext() with React.useContext() instead.',
2131 + ]);
2132 +
2133 statefulInst.setState({locale: 'gr'});
2134 await waitForAll([
2135 // Intl is below setState() so it might have been
@@ -2154,6 +2186,10 @@ describe('ReactIncremental', () => {
2186 ReactNoop.render(<Root />);
2187 await waitForAll([]);
2188
2189 + assertConsoleErrorDev([
2190 + 'Child uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
2191 + ]);
2192 +
2193 // Trigger an update in the middle of the tree
2194 instance.setState({});
2195 await waitForAll([]);
@@ -2199,7 +2235,9 @@ describe('ReactIncremental', () => {
2235
2236 // Init
2237 ReactNoop.render(<Root />);
2202 - await waitForAll([]);
2238 + await expect(async () => await waitForAll([])).toErrorDev([
2239 + 'ContextProvider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
2240 + ]);
2241
2242 // Trigger an update in the middle of the tree
2243 // This is necessary to reproduce the error as it currently exists.
@@ -2252,6 +2290,10 @@ describe('ReactIncremental', () => {
2290 'render',
2291 'componentDidUpdate',
2292 ]);
2293 +
2294 + assertConsoleErrorDev([
2295 + 'MyComponent uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
2296 + ]);
2297 });
2298
2299 // eslint-disable-next-line jest/no-disabled-tests
@@ -2384,6 +2426,10 @@ describe('ReactIncremental', () => {
2426 );
2427
2428 await waitForAll(['count:0']);
2429 + assertConsoleErrorDev([
2430 + 'TopContextProvider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
2431 + 'Child uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
2432 + ]);
2433 instance.updateCount();
2434 await waitForAll(['count:1']);
2435 });
@@ -2440,6 +2486,11 @@ describe('ReactIncremental', () => {
2486 );
2487
2488 await waitForAll(['count:0']);
2489 + assertConsoleErrorDev([
2490 + 'TopContextProvider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
2491 + 'MiddleContextProvider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
2492 + 'Child uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
2493 + ]);
2494 instance.updateCount();
2495 await waitForAll(['count:1']);
2496 });
@@ -2505,6 +2556,11 @@ describe('ReactIncremental', () => {
2556 );
2557
2558 await waitForAll(['count:0']);
2559 + assertConsoleErrorDev([
2560 + 'TopContextProvider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
2561 + 'MiddleContextProvider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
2562 + 'Child uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
2563 + ]);
2564 instance.updateCount();
2565 await waitForAll([]);
2566 });
@@ -2580,6 +2636,11 @@ describe('ReactIncremental', () => {
2636 );
2637
2638 await waitForAll(['count:0, name:brian']);
2639 + assertConsoleErrorDev([
2640 + 'TopContextProvider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
2641 + 'MiddleContextProvider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
2642 + 'Child uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
2643 + ]);
2644 topInstance.updateCount();
2645 await waitForAll([]);
2646 middleInstance.updateName('not brian');
@@ -2685,6 +2746,7 @@ describe('ReactIncremental', () => {
2746 await expect(async () => {
2747 await waitForAll([]);
2748 }).toErrorDev([
2749 + 'Boundary uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
2750 'Legacy context API has been detected within a strict-mode tree',
2751 ]);
2752 }
packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js
+8 -1
@@ -1211,7 +1211,14 @@ describe('ReactIncrementalErrorHandling', () => {
1211 <Connector />
1212 </Provider>,
1213 );
1214 - await waitForAll([]);
1214 +
1215 + await expect(async () => {
1216 + await waitForAll([]);
1217 + }).toErrorDev([
1218 + 'Provider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
1219 + 'Provider uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
1220 + 'Connector uses the legacy contextTypes API which will be removed soon. Use React.createContext() with React.useContext() instead.',
1221 + ]);
1222
1223 // If the context stack does not unwind, span will get 'abcde'
1224 expect(ReactNoop).toMatchRenderedOutput(<span prop="a" />);
packages/react-reconciler/src/__tests__/ReactNewContext-test.js
+10 -4
@@ -17,6 +17,7 @@ let gen;
17 let waitForAll;
18 let waitFor;
19 let waitForThrow;
20 +let assertConsoleErrorDev;
21
22 describe('ReactNewContext', () => {
23 beforeEach(() => {
@@ -28,10 +29,12 @@ describe('ReactNewContext', () => {
29 Scheduler = require('scheduler');
30 gen = require('random-seed');
31
31 - const InternalTestUtils = require('internal-test-utils');
32 - waitForAll = InternalTestUtils.waitForAll;
33 - waitFor = InternalTestUtils.waitFor;
34 - waitForThrow = InternalTestUtils.waitForThrow;
32 + ({
33 + waitForAll,
34 + waitFor,
35 + waitForThrow,
36 + assertConsoleErrorDev,
37 + } = require('internal-test-utils'));
38 });
39
40 afterEach(() => {
@@ -1032,6 +1035,9 @@ describe('ReactNewContext', () => {
1035 </LegacyProvider>,
1036 );
1037 await waitForAll(['LegacyProvider', 'App', 'Child']);
1038 + assertConsoleErrorDev([
1039 + 'LegacyProvider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
1040 + ]);
1041 expect(ReactNoop).toMatchRenderedOutput(<span prop="Child" />);
1042
1043 // Update App with same value (should bail out)
packages/react-server/src/ReactFizzClassComponent.js
+21 -3
@@ -370,7 +370,7 @@ function checkClassInstance(instance: any, ctor: any, newProps: any) {
370 didWarnAboutChildContextTypes.add(ctor);
371 console.error(
372 '%s uses the legacy childContextTypes API which was removed in React 19. ' +
373 - 'Use React.createContext() instead.',
373 + 'Use React.createContext() instead. (https://react.dev/link/legacy-context)',
374 name,
375 );
376 }
@@ -378,7 +378,8 @@ function checkClassInstance(instance: any, ctor: any, newProps: any) {
378 didWarnAboutContextTypes.add(ctor);
379 console.error(
380 '%s uses the legacy contextTypes API which was removed in React 19. ' +
381 - 'Use React.createContext() with static contextType instead.',
381 + 'Use React.createContext() with static contextType instead. ' +
382 + '(https://react.dev/link/legacy-context)',
383 name,
384 );
385 }
@@ -386,7 +387,7 @@ function checkClassInstance(instance: any, ctor: any, newProps: any) {
387 if (instance.contextTypes) {
388 console.error(
389 'contextTypes was defined as an instance property on %s. Use a static ' +
389 - 'property to define contextTypes instead.',
390 + 'property to define contextTypes instead. (https://react.dev/link/legacy-context)',
391 name,
392 );
393 }
@@ -403,6 +404,23 @@ function checkClassInstance(instance: any, ctor: any, newProps: any) {
404 name,
405 );
406 }
407 + if (ctor.childContextTypes && !didWarnAboutChildContextTypes.has(ctor)) {
408 + didWarnAboutChildContextTypes.add(ctor);
409 + console.error(
410 + '%s uses the legacy childContextTypes API which will soon be removed. ' +
411 + 'Use React.createContext() instead. (https://react.dev/link/legacy-context)',
412 + name,
413 + );
414 + }
415 + if (ctor.contextTypes && !didWarnAboutContextTypes.has(ctor)) {
416 + didWarnAboutContextTypes.add(ctor);
417 + console.error(
418 + '%s uses the legacy contextTypes API which will soon be removed. ' +
419 + 'Use React.createContext() with static contextType instead. ' +
420 + '(https://react.dev/link/legacy-context)',
421 + name,
422 + );
423 + }
424 }
425
426 if (typeof instance.componentShouldUpdate === 'function') {
packages/react-server/src/ReactFizzServer.js
+25 -14
@@ -1638,6 +1638,7 @@ function renderClassComponent(
1638 }
1639
1640 const didWarnAboutBadClass: {[string]: boolean} = {};
1641 +const didWarnAboutContextTypes: {[string]: boolean} = {};
1642 const didWarnAboutContextTypeOnFunctionComponent: {[string]: boolean} = {};
1643 const didWarnAboutGetDerivedStateOnFunctionComponent: {[string]: boolean} = {};
1644 let didWarnAboutReassigningProps = false;
@@ -1688,12 +1689,24 @@ function renderFunctionComponent(
1689 const actionStateMatchingIndex = getActionStateMatchingIndex();
1690
1691 if (__DEV__) {
1691 - if (disableLegacyContext && Component.contextTypes) {
1692 - console.error(
1693 - '%s uses the legacy contextTypes API which was removed in React 19. ' +
1694 - 'Use React.createContext() with React.useContext() instead.',
1695 - getComponentNameFromType(Component) || 'Unknown',
1696 - );
1692 + if (Component.contextTypes) {
1693 + const componentName = getComponentNameFromType(Component) || 'Unknown';
1694 + if (!didWarnAboutContextTypes[componentName]) {
1695 + didWarnAboutContextTypes[componentName] = true;
1696 + if (disableLegacyContext) {
1697 + console.error(
1698 + '%s uses the legacy contextTypes API which was removed in React 19. ' +
1699 + 'Use React.createContext() with React.useContext() instead.',
1700 + componentName,
1701 + );
1702 + } else {
1703 + console.error(
1704 + '%s uses the legacy contextTypes API which will be removed soon. ' +
1705 + 'Use React.createContext() with React.useContext() instead.',
1706 + componentName,
1707 + );
1708 + }
1709 + }
1710 }
1711 }
1712 if (__DEV__) {
@@ -1771,14 +1784,12 @@ function finishFunctionComponent(
1784
1785 function validateFunctionComponentInDev(Component: any): void {
1786 if (__DEV__) {
1774 - if (Component) {
1775 - if (Component.childContextTypes) {
1776 - console.error(
1777 - 'childContextTypes cannot be defined on a function component.\n' +
1778 - ' %s.childContextTypes = ...',
1779 - Component.displayName || Component.name || 'Component',
1780 - );
1781 - }
1787 + if (Component && Component.childContextTypes) {
1788 + console.error(
1789 + 'childContextTypes cannot be defined on a function component.\n' +
1790 + ' %s.childContextTypes = ...',
1791 + Component.displayName || Component.name || 'Component',
1792 + );
1793 }
1794
1795 if (
packages/react/src/__tests__/ReactCoffeeScriptClass-test.coffee
+14 -2
@@ -254,7 +254,12 @@ describe 'ReactCoffeeScriptClass', ->
254 render: ->
255 React.createElement Foo
256
257 - test React.createElement(Outer), 'SPAN', 'foo'
257 + expect(->
258 + test React.createElement(Outer), 'SPAN', 'foo'
259 + ).toErrorDev([
260 + 'Outer uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
261 + 'Foo uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
262 + ])
263
264 it 'renders only once when setting state in componentWillMount', ->
265 renderCount = 0
@@ -537,7 +542,14 @@ describe 'ReactCoffeeScriptClass', ->
542 render: ->
543 React.createElement Bar
544
540 - test React.createElement(Foo), 'DIV', 'bar-through-context'
545 + expect(->
546 + test React.createElement(Foo), 'DIV', 'bar-through-context'
547 + ).toErrorDev(
548 + [
549 + 'Foo uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
550 + 'Bar uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
551 + ],
552 + )
553
554 if !featureFlags.disableStringRefs
555 it 'supports string refs', ->
packages/react/src/__tests__/ReactContextValidator-test.js
+39 -29
@@ -66,11 +66,16 @@ describe('ReactContextValidator', () => {
66 let instance;
67 const container = document.createElement('div');
68 const root = ReactDOMClient.createRoot(container);
69 - await act(() => {
70 - root.render(
71 - <ComponentInFooBarContext ref={current => (instance = current)} />,
72 - );
73 - });
69 + await expect(async () => {
70 + await act(() => {
71 + root.render(
72 + <ComponentInFooBarContext ref={current => (instance = current)} />,
73 + );
74 + });
75 + }).toErrorDev([
76 + 'ComponentInFooBarContext uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
77 + 'Component uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
78 + ]);
79 expect(instance.childRef.current.context).toEqual({foo: 'abc'});
80 });
81
@@ -139,9 +144,14 @@ describe('ReactContextValidator', () => {
144
145 const container = document.createElement('div');
146 const root = ReactDOMClient.createRoot(container);
142 - await act(() => {
143 - root.render(<Parent foo="abc" />);
144 - });
147 + await expect(async () => {
148 + await act(() => {
149 + root.render(<Parent foo="abc" />);
150 + });
151 + }).toErrorDev([
152 + 'Parent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
153 + 'Component uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
154 + ]);
155
156 expect(constructorContext).toEqual({foo: 'abc'});
157 expect(renderContext).toEqual({foo: 'abc'});
@@ -187,11 +197,10 @@ describe('ReactContextValidator', () => {
197 await act(() => {
198 root.render(<ComponentA />);
199 });
190 - }).toErrorDev(
191 - 'ComponentA.childContextTypes is specified but there is no ' +
192 - 'getChildContext() method on the instance. You can either define ' +
193 - 'getChildContext() on ComponentA or remove childContextTypes from it.',
194 - );
200 + }).toErrorDev([
201 + 'ComponentA uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
202 + 'ComponentA.childContextTypes is specified but there is no getChildContext() method on the instance. You can either define getChildContext() on ComponentA or remove childContextTypes from it.',
203 + ]);
204
205 // Warnings should be deduped by component type
206 let container = document.createElement('div');
@@ -206,11 +215,10 @@ describe('ReactContextValidator', () => {
215 await act(() => {
216 root.render(<ComponentB />);
217 });
209 - }).toErrorDev(
210 - 'ComponentB.childContextTypes is specified but there is no ' +
211 - 'getChildContext() method on the instance. You can either define ' +
212 - 'getChildContext() on ComponentB or remove childContextTypes from it.',
213 - );
218 + }).toErrorDev([
219 + 'ComponentB uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
220 + 'ComponentB.childContextTypes is specified but there is no getChildContext() method on the instance. You can either define getChildContext() on ComponentB or remove childContextTypes from it.',
221 + ]);
222 });
223
224 // TODO (bvaughn) Remove this test and the associated behavior in the future.
@@ -259,9 +267,10 @@ describe('ReactContextValidator', () => {
267 root.render(<ParentContextProvider />);
268 });
269 }).toErrorDev([
262 - 'MiddleMissingContext.childContextTypes is specified but there is no ' +
263 - 'getChildContext() method on the instance. You can either define getChildContext() ' +
264 - 'on MiddleMissingContext or remove childContextTypes from it.',
270 + 'ParentContextProvider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
271 + 'MiddleMissingContext uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
272 + 'MiddleMissingContext.childContextTypes is specified but there is no getChildContext() method on the instance. You can either define getChildContext() on MiddleMissingContext or remove childContextTypes from it.',
273 + 'ChildContextConsumer uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
274 ]);
275 expect(childContext.bar).toBeUndefined();
276 expect(childContext.foo).toBe('FOO');
@@ -435,10 +444,11 @@ describe('ReactContextValidator', () => {
444 </ParentContextProvider>,
445 );
446 });
438 - }).toErrorDev(
439 - 'ComponentA declares both contextTypes and contextType static properties. ' +
440 - 'The legacy contextTypes property will be ignored.',
441 - );
447 + }).toErrorDev([
448 + 'ParentContextProvider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead',
449 + 'ComponentA uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
450 + 'ComponentA declares both contextTypes and contextType static properties. The legacy contextTypes property will be ignored.',
451 + ]);
452
453 // Warnings should be deduped by component type
454 let container = document.createElement('div');
@@ -461,10 +471,10 @@ describe('ReactContextValidator', () => {
471 </ParentContextProvider>,
472 );
473 });
464 - }).toErrorDev(
465 - 'ComponentB declares both contextTypes and contextType static properties. ' +
466 - 'The legacy contextTypes property will be ignored.',
467 - );
474 + }).toErrorDev([
475 + 'ComponentB declares both contextTypes and contextType static properties. The legacy contextTypes property will be ignored.',
476 + 'ComponentB uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
477 + ]);
478 });
479
480 // @gate enableRenderableContext || !__DEV__
packages/react/src/__tests__/ReactES6Class-test.js
+11
@@ -13,6 +13,7 @@ let PropTypes;
13 let React;
14 let ReactDOM;
15 let ReactDOMClient;
16 +let assertConsoleErrorDev;
17
18 describe('ReactES6Class', () => {
19 let container;
@@ -30,6 +31,7 @@ describe('ReactES6Class', () => {
31 React = require('react');
32 ReactDOM = require('react-dom');
33 ReactDOMClient = require('react-dom/client');
34 + ({assertConsoleErrorDev} = require('internal-test-utils'));
35 container = document.createElement('div');
36 root = ReactDOMClient.createRoot(container);
37 attachedListener = null;
@@ -287,6 +289,11 @@ describe('ReactES6Class', () => {
289 className: PropTypes.string,
290 };
291 runTest(<Outer />, 'SPAN', 'foo');
292 +
293 + assertConsoleErrorDev([
294 + 'Outer uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
295 + 'Foo uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
296 + ]);
297 });
298 }
299
@@ -579,6 +586,10 @@ describe('ReactES6Class', () => {
586 }
587 Foo.childContextTypes = {bar: PropTypes.string};
588 runTest(<Foo />, 'DIV', 'bar-through-context');
589 + assertConsoleErrorDev([
590 + 'Foo uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
591 + 'Bar uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
592 + ]);
593 });
594 }
595
packages/react/src/__tests__/ReactStrictMode-test.js
+30 -7
@@ -18,6 +18,7 @@ let act;
18 let useMemo;
19 let useState;
20 let useReducer;
21 +let assertConsoleErrorDev;
22
23 const ReactFeatureFlags = require('shared/ReactFeatureFlags');
24
@@ -28,7 +29,7 @@ describe('ReactStrictMode', () => {
29 ReactDOM = require('react-dom');
30 ReactDOMClient = require('react-dom/client');
31 ReactDOMServer = require('react-dom/server');
31 - act = require('internal-test-utils').act;
32 + ({act, assertConsoleErrorDev} = require('internal-test-utils'));
33 useMemo = React.useMemo;
34 useState = React.useState;
35 useReducer = React.useReducer;
@@ -1072,11 +1073,33 @@ describe('context legacy', () => {
1073
1074 const container = document.createElement('div');
1075 const root = ReactDOMClient.createRoot(container);
1075 - await expect(async () => {
1076 - await act(() => {
1077 - root.render(<Root />);
1078 - });
1079 - }).toErrorDev(
1076 + await act(() => {
1077 + root.render(<Root />);
1078 + });
1079 +
1080 + assertConsoleErrorDev([
1081 + 'LegacyContextProvider uses the legacy childContextTypes API ' +
1082 + 'which will soon be removed. Use React.createContext() instead. ' +
1083 + '(https://react.dev/link/legacy-context)' +
1084 + '\n in LegacyContextProvider (at **)' +
1085 + '\n in div (at **)' +
1086 + '\n in Root (at **)',
1087 + 'LegacyContextConsumer uses the legacy contextTypes API which ' +
1088 + 'will soon be removed. Use React.createContext() with static ' +
1089 + 'contextType instead. (https://react.dev/link/legacy-context)' +
1090 + '\n in LegacyContextConsumer (at **)' +
1091 + '\n in div (at **)' +
1092 + '\n in LegacyContextProvider (at **)' +
1093 + '\n in div (at **)' +
1094 + '\n in Root (at **)',
1095 + 'FunctionalLegacyContextConsumer uses the legacy contextTypes ' +
1096 + 'API which will be removed soon. Use React.createContext() ' +
1097 + 'with React.useContext() instead. (https://react.dev/link/legacy-context)' +
1098 + '\n in FunctionalLegacyContextConsumer (at **)' +
1099 + '\n in div (at **)' +
1100 + '\n in LegacyContextProvider (at **)' +
1101 + '\n in div (at **)' +
1102 + '\n in Root (at **)',
1103 'Legacy context API has been detected within a strict-mode tree.' +
1104 '\n\nThe old API will be supported in all 16.x releases, but applications ' +
1105 'using it should migrate to the new version.' +
@@ -1087,7 +1110,7 @@ describe('context legacy', () => {
1110 '\n in LegacyContextProvider (at **)' +
1111 '\n in div (at **)' +
1112 '\n in Root (at **)',
1090 - );
1113 + ]);
1114
1115 // Dedupe
1116 await act(() => {
packages/react/src/__tests__/ReactTypeScriptClass-test.ts
+9 -3
@@ -518,7 +518,10 @@ describe('ReactTypeScriptClass', function() {
518
519 if (!ReactFeatureFlags.disableLegacyContext) {
520 it('renders based on context in the constructor', function() {
521 - test(React.createElement(ProvideChildContextTypes), 'SPAN', 'foo');
521 + expect(() => test(React.createElement(ProvideChildContextTypes), 'SPAN', 'foo')).toErrorDev([
522 + 'ProvideChildContextTypes uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
523 + 'StateBasedOnContext uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.'
524 + ]);
525 });
526 }
527
@@ -687,8 +690,11 @@ describe('ReactTypeScriptClass', function() {
690 });
691
692 if (!ReactFeatureFlags.disableLegacyContext) {
690 - it('supports this.context passed via getChildContext', function() {
691 - test(React.createElement(ProvideContext), 'DIV', 'bar-through-context');
693 + it('supports this.context passed via getChildContext', () => {
694 + expect(() => test(React.createElement(ProvideContext), 'DIV', 'bar-through-context')).toErrorDev([
695 + 'ProvideContext uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
696 + 'ReadContext uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
697 +] );
698 });
699 }
700
packages/react/src/__tests__/createReactClassIntegration-test.js
+6 -1
@@ -10,6 +10,7 @@
10 'use strict';
11
12 let act;
13 +let assertConsoleErrorDev;
14
15 let PropTypes;
16 let React;
@@ -19,7 +20,7 @@ let createReactClass;
20 describe('create-react-class-integration', () => {
21 beforeEach(() => {
22 jest.resetModules();
22 - ({act} = require('internal-test-utils'));
23 + ({act, assertConsoleErrorDev} = require('internal-test-utils'));
24 PropTypes = require('prop-types');
25 React = require('react');
26 ReactDOMClient = require('react-dom/client');
@@ -336,6 +337,10 @@ describe('create-react-class-integration', () => {
337 await act(() => {
338 root.render(<Outer />);
339 });
340 + assertConsoleErrorDev([
341 + 'Component uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
342 + 'Component uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
343 + ]);
344 expect(container.firstChild.className).toBe('foo');
345 });
346