diff --git a/packages/react-dom/src/__tests__/ReactDOMFiber-test.js b/packages/react-dom/src/__tests__/ReactDOMFiber-test.js index a1633fa1e625c..7c701219ec3de 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFiber-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFiber-test.js @@ -13,10 +13,12 @@ let React; let ReactDOM; let PropTypes; let ReactDOMClient; -let root; let Scheduler; + let act; +let assertConsoleErrorDev; let assertLog; +let root; describe('ReactDOMFiber', () => { let container; @@ -29,7 +31,7 @@ describe('ReactDOMFiber', () => { ReactDOMClient = require('react-dom/client'); Scheduler = require('scheduler'); act = require('internal-test-utils').act; - assertLog = require('internal-test-utils').assertLog; + ({assertConsoleErrorDev, assertLog} = require('internal-test-utils')); container = document.createElement('div'); document.body.appendChild(container); @@ -732,6 +734,10 @@ describe('ReactDOMFiber', () => { await act(async () => { root.render(); }); + assertConsoleErrorDev([ + 'Parent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + 'Component uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.', + ]); expect(container.innerHTML).toBe(''); expect(portalContainer.innerHTML).toBe('
bar
'); }); diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js index d534df76a55d8..0320272b9cbb4 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js @@ -27,6 +27,8 @@ let ReactDOMFizzServer; let ReactDOMFizzStatic; let Suspense; let SuspenseList; + +let assertConsoleErrorDev; let useSyncExternalStore; let useSyncExternalStoreWithSelector; let use; @@ -116,12 +118,14 @@ describe('ReactDOMFizzServer', () => { useActionState = React.useActionState; } - const InternalTestUtils = require('internal-test-utils'); - waitForAll = InternalTestUtils.waitForAll; - waitFor = InternalTestUtils.waitFor; - waitForPaint = InternalTestUtils.waitForPaint; - assertLog = InternalTestUtils.assertLog; - clientAct = InternalTestUtils.act; + ({ + assertConsoleErrorDev, + assertLog, + act: clientAct, + waitFor, + waitForAll, + waitForPaint, + } = require('internal-test-utils')); if (gate(flags => flags.source)) { // The `with-selector` module composes the main `use-sync-external-store` @@ -1950,6 +1954,10 @@ describe('ReactDOMFizzServer', () => { ); pipe(writable); }); + assertConsoleErrorDev([ + 'TestProvider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + 'TestConsumer uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.', + ]); expect(getVisibleChildren(container)).toEqual(
Loading: A diff --git a/packages/react-dom/src/__tests__/ReactDOMLegacyFiber-test.js b/packages/react-dom/src/__tests__/ReactDOMLegacyFiber-test.js index b81dea9f18263..c799e3b904b86 100644 --- a/packages/react-dom/src/__tests__/ReactDOMLegacyFiber-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMLegacyFiber-test.js @@ -786,7 +786,12 @@ describe('ReactDOMLegacyFiber', () => { } } - ReactDOM.render(, container); + expect(() => { + ReactDOM.render(, container); + }).toErrorDev([ + 'Parent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + 'Component uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.', + ]); expect(container.innerHTML).toBe(''); expect(portalContainer.innerHTML).toBe('
bar
'); }); @@ -829,7 +834,13 @@ describe('ReactDOMLegacyFiber', () => { } } - const instance = ReactDOM.render(, container); + let instance; + expect(() => { + instance = ReactDOM.render(, container); + }).toErrorDev([ + 'Parent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + 'Component uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.', + ]); expect(portalContainer.innerHTML).toBe('
initial-initial
'); expect(container.innerHTML).toBe(''); instance.setState({bar: 'changed'}); @@ -871,7 +882,12 @@ describe('ReactDOMLegacyFiber', () => { } } - ReactDOM.render(, container); + expect(() => { + ReactDOM.render(, container); + }).toErrorDev([ + 'Parent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + 'Component uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.', + ]); expect(portalContainer.innerHTML).toBe('
initial-initial
'); expect(container.innerHTML).toBe(''); ReactDOM.render(, container); diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationLegacyContext-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationLegacyContext-test.js index c28bf2d8e4e2c..130bd2310e24a 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationLegacyContext-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationLegacyContext-test.js @@ -80,6 +80,7 @@ describe('ReactDOMServerIntegration', () => { , + 2, ); expect(e.textContent).toBe('purple'); }); @@ -94,6 +95,7 @@ describe('ReactDOMServerIntegration', () => { , + 2, ); expect(e.textContent).toBe('purple'); }); @@ -110,6 +112,7 @@ describe('ReactDOMServerIntegration', () => { , + 1, ); expect(e.textContent).toBe(''); }); @@ -124,6 +127,7 @@ describe('ReactDOMServerIntegration', () => { , + 1, ); expect(e.textContent).toBe(''); }); @@ -141,6 +145,7 @@ describe('ReactDOMServerIntegration', () => { , + 2, ); expect(e.textContent).toBe(''); }); @@ -158,6 +163,7 @@ describe('ReactDOMServerIntegration', () => { , + 2, ); expect(e.textContent).toBe(''); }); @@ -174,6 +180,7 @@ describe('ReactDOMServerIntegration', () => { , + 2, ); expect(e.textContent).toBe('purple'); }); @@ -190,6 +197,7 @@ describe('ReactDOMServerIntegration', () => { , + 2, ); expect(e.textContent).toBe('red'); }); @@ -228,7 +236,7 @@ describe('ReactDOMServerIntegration', () => { text2: PropTypes.string, }; - const e = await render(); + const e = await render(, 3); expect(e.querySelector('#first').textContent).toBe('purple'); expect(e.querySelector('#second').textContent).toBe('red'); }); @@ -254,7 +262,7 @@ describe('ReactDOMServerIntegration', () => { }; Child.contextTypes = {text: PropTypes.string}; - const e = await render(); + const e = await render(, 2); expect(e.textContent).toBe('foo'); }, ); @@ -278,7 +286,8 @@ describe('ReactDOMServerIntegration', () => { } const e = await render( , - render === clientRenderOnBadMarkup ? 2 : 1, + // Some warning is not de-duped and logged again on the client retry render. + render === clientRenderOnBadMarkup ? 3 : 2, ); expect(e.textContent).toBe('nope'); }, diff --git a/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js b/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js index f45069e91539c..2d7696eee5c58 100644 --- a/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js @@ -36,6 +36,7 @@ describe('ReactErrorBoundaries', () => { let RetryErrorBoundary; let Normal; let assertLog; + let assertConsoleErrorDev; beforeEach(() => { jest.useFakeTimers(); @@ -47,8 +48,7 @@ describe('ReactErrorBoundaries', () => { act = require('internal-test-utils').act; Scheduler = require('scheduler'); - const InternalTestUtils = require('internal-test-utils'); - assertLog = InternalTestUtils.assertLog; + ({assertLog, assertConsoleErrorDev} = require('internal-test-utils')); BrokenConstructor = class extends React.Component { constructor(props) { @@ -895,6 +895,9 @@ describe('ReactErrorBoundaries', () => { , ); }); + assertConsoleErrorDev([ + 'BrokenComponentWillMountWithContext uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + ]); expect(container.firstChild.textContent).toBe('Caught an error: Hello.'); }); diff --git a/packages/react-dom/src/__tests__/ReactFunctionComponent-test.js b/packages/react-dom/src/__tests__/ReactFunctionComponent-test.js index d3b8926ecd3ea..d2b1aaa4346d0 100644 --- a/packages/react-dom/src/__tests__/ReactFunctionComponent-test.js +++ b/packages/react-dom/src/__tests__/ReactFunctionComponent-test.js @@ -13,6 +13,7 @@ let PropTypes; let React; let ReactDOMClient; let act; +let assertConsoleErrorDev; function FunctionComponent(props) { return
{props.name}
; @@ -24,7 +25,7 @@ describe('ReactFunctionComponent', () => { PropTypes = require('prop-types'); React = require('react'); ReactDOMClient = require('react-dom/client'); - act = require('internal-test-utils').act; + ({act, assertConsoleErrorDev} = require('internal-test-utils')); }); it('should render stateless component', async () => { @@ -109,6 +110,11 @@ describe('ReactFunctionComponent', () => { root.render(); }); + assertConsoleErrorDev([ + 'GrandParent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + 'Child uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.', + ]); + expect(el.textContent).toBe('test'); await act(() => { @@ -472,6 +478,10 @@ describe('ReactFunctionComponent', () => { await act(() => { root.render(); }); + assertConsoleErrorDev([ + 'Parent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + 'Child uses the legacy contextTypes API which will be removed soon. Use React.createContext() with React.useContext() instead.', + ]); expect(el.textContent).toBe('en'); }); diff --git a/packages/react-dom/src/__tests__/ReactLegacyCompositeComponent-test.js b/packages/react-dom/src/__tests__/ReactLegacyCompositeComponent-test.js index eaf49b7527e38..cb8b07e72bdcf 100644 --- a/packages/react-dom/src/__tests__/ReactLegacyCompositeComponent-test.js +++ b/packages/react-dom/src/__tests__/ReactLegacyCompositeComponent-test.js @@ -14,7 +14,9 @@ let ReactDOM; let findDOMNode; let ReactDOMClient; let PropTypes; + let act; +let assertConsoleErrorDev; describe('ReactLegacyCompositeComponent', () => { beforeEach(() => { @@ -26,7 +28,7 @@ describe('ReactLegacyCompositeComponent', () => { ReactDOM.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE .findDOMNode; PropTypes = require('prop-types'); - act = require('internal-test-utils').act; + ({act, assertConsoleErrorDev} = require('internal-test-utils')); }); // @gate !disableLegacyMode @@ -119,6 +121,10 @@ describe('ReactLegacyCompositeComponent', () => { await act(() => { root.render( (component = current)} />); }); + assertConsoleErrorDev([ + 'Child uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + 'Grandchild uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.', + ]); expect(findDOMNode(component).innerHTML).toBe('bar'); }); @@ -183,6 +189,11 @@ describe('ReactLegacyCompositeComponent', () => { expect(parentInstance.state.flag).toBe(false); expect(childInstance.context).toEqual({foo: 'bar', flag: false}); + assertConsoleErrorDev([ + 'Parent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + 'Child uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.', + ]); + await act(() => { parentInstance.setState({flag: true}); }); @@ -242,6 +253,11 @@ describe('ReactLegacyCompositeComponent', () => { root.render( (wrapper = current)} />); }); + assertConsoleErrorDev([ + 'Parent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + 'Child uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.', + ]); + expect(wrapper.parentRef.current.state.flag).toEqual(true); expect(wrapper.childRef.current.context).toEqual({flag: true}); @@ -317,6 +333,13 @@ describe('ReactLegacyCompositeComponent', () => { root.render(); }); + assertConsoleErrorDev([ + 'Parent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + 'Child uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + 'Child uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.', + 'Grandchild uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.', + ]); + expect(childInstance.context).toEqual({foo: 'bar', depth: 0}); expect(grandchildInstance.context).toEqual({foo: 'bar', depth: 1}); }); @@ -369,6 +392,9 @@ describe('ReactLegacyCompositeComponent', () => { await act(() => { root.render( (parentInstance = current)} />); }); + assertConsoleErrorDev([ + 'Parent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + ]); expect(childInstance).toBeNull(); @@ -376,6 +402,10 @@ describe('ReactLegacyCompositeComponent', () => { await act(() => { parentInstance.setState({flag: true}); }); + assertConsoleErrorDev([ + 'Child uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.', + ]); + expect(parentInstance.state.flag).toBe(true); expect(childInstance.context).toEqual({foo: 'bar', depth: 0}); @@ -435,7 +465,12 @@ describe('ReactLegacyCompositeComponent', () => { } const div = document.createElement('div'); - ReactDOM.render(, div); + expect(() => { + ReactDOM.render(, div); + }).toErrorDev([ + 'Parent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + 'Leaf uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.', + ]); expect(div.children[0].innerHTML).toBe('noise'); div.children[0].innerHTML = 'aliens'; div.children[0].id = 'aliens'; @@ -537,20 +572,26 @@ describe('ReactLegacyCompositeComponent', () => { const div = document.createElement('div'); let parentInstance = null; - ReactDOM.render( - (parentInstance = inst)}> - - A1 - A2 - - - - B1 - B2 - - , - div, - ); + expect(() => { + ReactDOM.render( + (parentInstance = inst)}> + + A1 + A2 + + + + B1 + B2 + + , + div, + ); + }).toErrorDev([ + 'Parent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + 'GrandChild uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.', + 'ChildWithContext uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.', + ]); parentInstance.setState({ foo: 'def', @@ -733,12 +774,17 @@ describe('ReactLegacyCompositeComponent', () => { } const div = document.createElement('div'); - ReactDOM.render( - - - , - div, - ); + expect(() => { + ReactDOM.render( + + + , + div, + ); + }).toErrorDev([ + 'Parent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + 'Component uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.', + ]); }); it('should replace state in legacy mode', async () => { diff --git a/packages/react-dom/src/__tests__/ReactLegacyErrorBoundaries-test.internal.js b/packages/react-dom/src/__tests__/ReactLegacyErrorBoundaries-test.internal.js index f45fae7bbd7e1..2444900ec6fef 100644 --- a/packages/react-dom/src/__tests__/ReactLegacyErrorBoundaries-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactLegacyErrorBoundaries-test.internal.js @@ -849,6 +849,9 @@ describe('ReactLegacyErrorBoundaries', () => { , container, ); + assertConsoleErrorDev([ + 'BrokenComponentWillMountWithContext uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + ]); expect(container.firstChild.textContent).toBe('Caught an error: Hello.'); }); diff --git a/packages/react-dom/src/__tests__/ReactServerRendering-test.js b/packages/react-dom/src/__tests__/ReactServerRendering-test.js index 065f7cadd7f85..71e6ce7224be5 100644 --- a/packages/react-dom/src/__tests__/ReactServerRendering-test.js +++ b/packages/react-dom/src/__tests__/ReactServerRendering-test.js @@ -371,11 +371,17 @@ describe('ReactDOMServer', () => { text: PropTypes.string, }; - const markup = ReactDOMServer.renderToStaticMarkup( - - - , - ); + let markup; + expect(() => { + markup = ReactDOMServer.renderToStaticMarkup( + + + , + ); + }).toErrorDev([ + 'ContextProvider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + 'Component uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.', + ]); expect(markup).toContain('hello, world'); }); diff --git a/packages/react-native-renderer/src/__tests__/ReactNativeEvents-test.internal.js b/packages/react-native-renderer/src/__tests__/ReactNativeEvents-test.internal.js index ed0521bedef9f..46b2ad9cf1fc7 100644 --- a/packages/react-native-renderer/src/__tests__/ReactNativeEvents-test.internal.js +++ b/packages/react-native-renderer/src/__tests__/ReactNativeEvents-test.internal.js @@ -227,27 +227,31 @@ test('handles events on text nodes', () => { } const log = []; - ReactNative.render( - - - log.push('string touchend')} - onTouchEndCapture={() => log.push('string touchend capture')} - onTouchStart={() => log.push('string touchstart')} - onTouchStartCapture={() => log.push('string touchstart capture')}> - Text Content - - log.push('number touchend')} - onTouchEndCapture={() => log.push('number touchend capture')} - onTouchStart={() => log.push('number touchstart')} - onTouchStartCapture={() => log.push('number touchstart capture')}> - {123} + expect(() => { + ReactNative.render( + + + log.push('string touchend')} + onTouchEndCapture={() => log.push('string touchend capture')} + onTouchStart={() => log.push('string touchstart')} + onTouchStartCapture={() => log.push('string touchstart capture')}> + Text Content + + log.push('number touchend')} + onTouchEndCapture={() => log.push('number touchend capture')} + onTouchStart={() => log.push('number touchstart')} + onTouchStartCapture={() => log.push('number touchstart capture')}> + {123} + - - , - 1, - ); + , + 1, + ); + }).toErrorDev([ + 'ContextHack uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + ]); expect(UIManager.createView).toHaveBeenCalledTimes(5); diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index 5c759d9a52cf1..d53ee83777cb0 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -316,6 +316,7 @@ let didReceiveUpdate: boolean = false; let didWarnAboutBadClass; let didWarnAboutContextTypeOnFunctionComponent; +let didWarnAboutContextTypes; let didWarnAboutGetDerivedStateOnFunctionComponent; let didWarnAboutFunctionRefs; export let didWarnAboutReassigningProps: boolean; @@ -326,6 +327,7 @@ let didWarnAboutDefaultPropsOnFunctionComponent; if (__DEV__) { didWarnAboutBadClass = ({}: {[string]: boolean}); didWarnAboutContextTypeOnFunctionComponent = ({}: {[string]: boolean}); + didWarnAboutContextTypes = ({}: {[string]: boolean}); didWarnAboutGetDerivedStateOnFunctionComponent = ({}: {[string]: boolean}); didWarnAboutFunctionRefs = ({}: {[string]: boolean}); didWarnAboutReassigningProps = false; @@ -1130,12 +1132,27 @@ function updateFunctionComponent( // in updateFuntionComponent but only on mount validateFunctionComponentInDev(workInProgress, workInProgress.type); - if (disableLegacyContext && Component.contextTypes) { - console.error( - '%s uses the legacy contextTypes API which was removed in React 19. ' + - 'Use React.createContext() with React.useContext() instead.', - getComponentNameFromType(Component) || 'Unknown', - ); + if (Component.contextTypes) { + const componentName = getComponentNameFromType(Component) || 'Unknown'; + + if (!didWarnAboutContextTypes[componentName]) { + didWarnAboutContextTypes[componentName] = true; + if (disableLegacyContext) { + console.error( + '%s uses the legacy contextTypes API which was removed in React 19. ' + + 'Use React.createContext() with React.useContext() instead. ' + + '(https://react.dev/link/legacy-context)', + componentName, + ); + } else { + console.error( + '%s uses the legacy contextTypes API which will be removed soon. ' + + 'Use React.createContext() with React.useContext() instead. ' + + '(https://react.dev/link/legacy-context)', + componentName, + ); + } + } } } } @@ -1923,14 +1940,12 @@ function mountIncompleteClassComponent( function validateFunctionComponentInDev(workInProgress: Fiber, Component: any) { if (__DEV__) { - if (Component) { - if (Component.childContextTypes) { - console.error( - 'childContextTypes cannot be defined on a function component.\n' + - ' %s.childContextTypes = ...', - Component.displayName || Component.name || 'Component', - ); - } + if (Component && Component.childContextTypes) { + console.error( + 'childContextTypes cannot be defined on a function component.\n' + + ' %s.childContextTypes = ...', + Component.displayName || Component.name || 'Component', + ); } if (!enableRefAsProp && workInProgress.ref !== null) { let info = ''; diff --git a/packages/react-reconciler/src/ReactFiberClassComponent.js b/packages/react-reconciler/src/ReactFiberClassComponent.js index 4f7ef8530a908..f1419d1aad047 100644 --- a/packages/react-reconciler/src/ReactFiberClassComponent.js +++ b/packages/react-reconciler/src/ReactFiberClassComponent.js @@ -393,7 +393,7 @@ function checkClassInstance(workInProgress: Fiber, ctor: any, newProps: any) { didWarnAboutChildContextTypes.add(ctor); console.error( '%s uses the legacy childContextTypes API which was removed in React 19. ' + - 'Use React.createContext() instead.', + 'Use React.createContext() instead. (https://react.dev/link/legacy-context)', name, ); } @@ -401,7 +401,8 @@ function checkClassInstance(workInProgress: Fiber, ctor: any, newProps: any) { didWarnAboutContextTypes.add(ctor); console.error( '%s uses the legacy contextTypes API which was removed in React 19. ' + - 'Use React.createContext() with static contextType instead.', + 'Use React.createContext() with static contextType instead. ' + + '(https://react.dev/link/legacy-context)', name, ); } @@ -426,6 +427,23 @@ function checkClassInstance(workInProgress: Fiber, ctor: any, newProps: any) { name, ); } + if (ctor.childContextTypes && !didWarnAboutChildContextTypes.has(ctor)) { + didWarnAboutChildContextTypes.add(ctor); + console.error( + '%s uses the legacy childContextTypes API which will soon be removed. ' + + 'Use React.createContext() instead. (https://react.dev/link/legacy-context)', + name, + ); + } + if (ctor.contextTypes && !didWarnAboutContextTypes.has(ctor)) { + didWarnAboutContextTypes.add(ctor); + console.error( + '%s uses the legacy contextTypes API which will soon be removed. ' + + 'Use React.createContext() with static contextType instead. ' + + '(https://react.dev/link/legacy-context)', + name, + ); + } } if (typeof instance.componentShouldUpdate === 'function') { diff --git a/packages/react-reconciler/src/__tests__/ReactIncremental-test.js b/packages/react-reconciler/src/__tests__/ReactIncremental-test.js index 2ce110bebe55a..399e4c01b98cb 100644 --- a/packages/react-reconciler/src/__tests__/ReactIncremental-test.js +++ b/packages/react-reconciler/src/__tests__/ReactIncremental-test.js @@ -14,6 +14,8 @@ let React; let ReactNoop; let Scheduler; let PropTypes; + +let assertConsoleErrorDev; let waitForAll; let waitFor; let waitForThrow; @@ -27,11 +29,13 @@ describe('ReactIncremental', () => { Scheduler = require('scheduler'); PropTypes = require('prop-types'); - const InternalTestUtils = require('internal-test-utils'); - waitForAll = InternalTestUtils.waitForAll; - waitFor = InternalTestUtils.waitFor; - waitForThrow = InternalTestUtils.waitForThrow; - assertLog = InternalTestUtils.assertLog; + ({ + assertConsoleErrorDev, + waitForAll, + waitFor, + waitForThrow, + assertLog, + } = require('internal-test-utils')); }); // Note: This is based on a similar component we use in www. We can delete @@ -1793,6 +1797,11 @@ describe('ReactIncremental', () => { 'ShowLocale {"locale":"fr"}', 'ShowBoth {"locale":"fr"}', ]); + assertConsoleErrorDev([ + 'Intl uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + 'ShowLocale uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.', + 'ShowBoth uses the legacy contextTypes API which will be removed soon. Use React.createContext() with React.useContext() instead.', + ]); ReactNoop.render( @@ -1843,6 +1852,10 @@ describe('ReactIncremental', () => { 'ShowBoth {"locale":"en","route":"/about"}', 'ShowBoth {"locale":"en"}', ]); + assertConsoleErrorDev([ + 'Router uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + 'ShowRoute uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.', + ]); }); // @gate !disableLegacyContext @@ -1876,6 +1889,10 @@ describe('ReactIncremental', () => { 'Recurse {"n":1}', 'Recurse {"n":0}', ]); + assertConsoleErrorDev([ + 'Recurse uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + 'Recurse uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.', + ]); }); // @gate enableLegacyHidden && !disableLegacyContext @@ -1925,6 +1942,10 @@ describe('ReactIncremental', () => { 'ShowLocale {"locale":"fr"}', 'ShowLocale {"locale":"fr"}', ]); + assertConsoleErrorDev([ + 'Intl uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + 'ShowLocale uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.', + ]); await waitForAll([ 'ShowLocale {"locale":"fr"}', @@ -2012,6 +2033,11 @@ describe('ReactIncremental', () => { 'ShowLocaleClass:read {"locale":"fr"}', 'ShowLocaleFn:read {"locale":"fr"}', ]); + assertConsoleErrorDev([ + 'Intl uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + 'ShowLocaleClass uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.', + 'ShowLocaleFn uses the legacy contextTypes API which will be removed soon. Use React.createContext() with React.useContext() instead.', + ]); statefulInst.setState({x: 1}); await waitForAll([]); @@ -2098,6 +2124,12 @@ describe('ReactIncremental', () => { 'ShowLocaleFn:read {"locale":"fr"}', ]); + assertConsoleErrorDev([ + 'Intl uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + 'ShowLocaleClass uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.', + 'ShowLocaleFn uses the legacy contextTypes API which will be removed soon. Use React.createContext() with React.useContext() instead.', + ]); + statefulInst.setState({locale: 'gr'}); await waitForAll([ // Intl is below setState() so it might have been @@ -2154,6 +2186,10 @@ describe('ReactIncremental', () => { ReactNoop.render(); await waitForAll([]); + assertConsoleErrorDev([ + 'Child uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + ]); + // Trigger an update in the middle of the tree instance.setState({}); await waitForAll([]); @@ -2199,7 +2235,9 @@ describe('ReactIncremental', () => { // Init ReactNoop.render(); - await waitForAll([]); + await expect(async () => await waitForAll([])).toErrorDev([ + 'ContextProvider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + ]); // Trigger an update in the middle of the tree // This is necessary to reproduce the error as it currently exists. @@ -2252,6 +2290,10 @@ describe('ReactIncremental', () => { 'render', 'componentDidUpdate', ]); + + assertConsoleErrorDev([ + 'MyComponent uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.', + ]); }); // eslint-disable-next-line jest/no-disabled-tests @@ -2384,6 +2426,10 @@ describe('ReactIncremental', () => { ); await waitForAll(['count:0']); + assertConsoleErrorDev([ + 'TopContextProvider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + 'Child uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.', + ]); instance.updateCount(); await waitForAll(['count:1']); }); @@ -2440,6 +2486,11 @@ describe('ReactIncremental', () => { ); await waitForAll(['count:0']); + assertConsoleErrorDev([ + 'TopContextProvider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + 'MiddleContextProvider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + 'Child uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.', + ]); instance.updateCount(); await waitForAll(['count:1']); }); @@ -2505,6 +2556,11 @@ describe('ReactIncremental', () => { ); await waitForAll(['count:0']); + assertConsoleErrorDev([ + 'TopContextProvider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + 'MiddleContextProvider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + 'Child uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.', + ]); instance.updateCount(); await waitForAll([]); }); @@ -2580,6 +2636,11 @@ describe('ReactIncremental', () => { ); await waitForAll(['count:0, name:brian']); + assertConsoleErrorDev([ + 'TopContextProvider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + 'MiddleContextProvider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + 'Child uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.', + ]); topInstance.updateCount(); await waitForAll([]); middleInstance.updateName('not brian'); @@ -2685,6 +2746,7 @@ describe('ReactIncremental', () => { await expect(async () => { await waitForAll([]); }).toErrorDev([ + 'Boundary uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.', 'Legacy context API has been detected within a strict-mode tree', ]); } diff --git a/packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js b/packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js index 0cee8150cb15f..91658c562c948 100644 --- a/packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js @@ -1211,7 +1211,14 @@ describe('ReactIncrementalErrorHandling', () => { , ); - await waitForAll([]); + + await expect(async () => { + await waitForAll([]); + }).toErrorDev([ + 'Provider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + 'Provider uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.', + 'Connector uses the legacy contextTypes API which will be removed soon. Use React.createContext() with React.useContext() instead.', + ]); // If the context stack does not unwind, span will get 'abcde' expect(ReactNoop).toMatchRenderedOutput(); diff --git a/packages/react-reconciler/src/__tests__/ReactNewContext-test.js b/packages/react-reconciler/src/__tests__/ReactNewContext-test.js index 59d88a9ffa104..b8fbca8339f5b 100644 --- a/packages/react-reconciler/src/__tests__/ReactNewContext-test.js +++ b/packages/react-reconciler/src/__tests__/ReactNewContext-test.js @@ -17,6 +17,7 @@ let gen; let waitForAll; let waitFor; let waitForThrow; +let assertConsoleErrorDev; describe('ReactNewContext', () => { beforeEach(() => { @@ -28,10 +29,12 @@ describe('ReactNewContext', () => { Scheduler = require('scheduler'); gen = require('random-seed'); - const InternalTestUtils = require('internal-test-utils'); - waitForAll = InternalTestUtils.waitForAll; - waitFor = InternalTestUtils.waitFor; - waitForThrow = InternalTestUtils.waitForThrow; + ({ + waitForAll, + waitFor, + waitForThrow, + assertConsoleErrorDev, + } = require('internal-test-utils')); }); afterEach(() => { @@ -1032,6 +1035,9 @@ describe('ReactNewContext', () => { , ); await waitForAll(['LegacyProvider', 'App', 'Child']); + assertConsoleErrorDev([ + 'LegacyProvider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + ]); expect(ReactNoop).toMatchRenderedOutput(); // Update App with same value (should bail out) diff --git a/packages/react-server/src/ReactFizzClassComponent.js b/packages/react-server/src/ReactFizzClassComponent.js index 6ef87f100b57d..08b4aaa94c79b 100644 --- a/packages/react-server/src/ReactFizzClassComponent.js +++ b/packages/react-server/src/ReactFizzClassComponent.js @@ -370,7 +370,7 @@ function checkClassInstance(instance: any, ctor: any, newProps: any) { didWarnAboutChildContextTypes.add(ctor); console.error( '%s uses the legacy childContextTypes API which was removed in React 19. ' + - 'Use React.createContext() instead.', + 'Use React.createContext() instead. (https://react.dev/link/legacy-context)', name, ); } @@ -378,7 +378,8 @@ function checkClassInstance(instance: any, ctor: any, newProps: any) { didWarnAboutContextTypes.add(ctor); console.error( '%s uses the legacy contextTypes API which was removed in React 19. ' + - 'Use React.createContext() with static contextType instead.', + 'Use React.createContext() with static contextType instead. ' + + '(https://react.dev/link/legacy-context)', name, ); } @@ -386,7 +387,7 @@ function checkClassInstance(instance: any, ctor: any, newProps: any) { if (instance.contextTypes) { console.error( 'contextTypes was defined as an instance property on %s. Use a static ' + - 'property to define contextTypes instead.', + 'property to define contextTypes instead. (https://react.dev/link/legacy-context)', name, ); } @@ -403,6 +404,23 @@ function checkClassInstance(instance: any, ctor: any, newProps: any) { name, ); } + if (ctor.childContextTypes && !didWarnAboutChildContextTypes.has(ctor)) { + didWarnAboutChildContextTypes.add(ctor); + console.error( + '%s uses the legacy childContextTypes API which will soon be removed. ' + + 'Use React.createContext() instead. (https://react.dev/link/legacy-context)', + name, + ); + } + if (ctor.contextTypes && !didWarnAboutContextTypes.has(ctor)) { + didWarnAboutContextTypes.add(ctor); + console.error( + '%s uses the legacy contextTypes API which will soon be removed. ' + + 'Use React.createContext() with static contextType instead. ' + + '(https://react.dev/link/legacy-context)', + name, + ); + } } if (typeof instance.componentShouldUpdate === 'function') { diff --git a/packages/react-server/src/ReactFizzServer.js b/packages/react-server/src/ReactFizzServer.js index afdc9efbb282c..ce255e58a84c5 100644 --- a/packages/react-server/src/ReactFizzServer.js +++ b/packages/react-server/src/ReactFizzServer.js @@ -1638,6 +1638,7 @@ function renderClassComponent( } const didWarnAboutBadClass: {[string]: boolean} = {}; +const didWarnAboutContextTypes: {[string]: boolean} = {}; const didWarnAboutContextTypeOnFunctionComponent: {[string]: boolean} = {}; const didWarnAboutGetDerivedStateOnFunctionComponent: {[string]: boolean} = {}; let didWarnAboutReassigningProps = false; @@ -1688,12 +1689,24 @@ function renderFunctionComponent( const actionStateMatchingIndex = getActionStateMatchingIndex(); if (__DEV__) { - if (disableLegacyContext && Component.contextTypes) { - console.error( - '%s uses the legacy contextTypes API which was removed in React 19. ' + - 'Use React.createContext() with React.useContext() instead.', - getComponentNameFromType(Component) || 'Unknown', - ); + if (Component.contextTypes) { + const componentName = getComponentNameFromType(Component) || 'Unknown'; + if (!didWarnAboutContextTypes[componentName]) { + didWarnAboutContextTypes[componentName] = true; + if (disableLegacyContext) { + console.error( + '%s uses the legacy contextTypes API which was removed in React 19. ' + + 'Use React.createContext() with React.useContext() instead.', + componentName, + ); + } else { + console.error( + '%s uses the legacy contextTypes API which will be removed soon. ' + + 'Use React.createContext() with React.useContext() instead.', + componentName, + ); + } + } } } if (__DEV__) { @@ -1771,14 +1784,12 @@ function finishFunctionComponent( function validateFunctionComponentInDev(Component: any): void { if (__DEV__) { - if (Component) { - if (Component.childContextTypes) { - console.error( - 'childContextTypes cannot be defined on a function component.\n' + - ' %s.childContextTypes = ...', - Component.displayName || Component.name || 'Component', - ); - } + if (Component && Component.childContextTypes) { + console.error( + 'childContextTypes cannot be defined on a function component.\n' + + ' %s.childContextTypes = ...', + Component.displayName || Component.name || 'Component', + ); } if ( diff --git a/packages/react/src/__tests__/ReactCoffeeScriptClass-test.coffee b/packages/react/src/__tests__/ReactCoffeeScriptClass-test.coffee index 4a4d07a78e592..22ef789f4f6e9 100644 --- a/packages/react/src/__tests__/ReactCoffeeScriptClass-test.coffee +++ b/packages/react/src/__tests__/ReactCoffeeScriptClass-test.coffee @@ -254,7 +254,12 @@ describe 'ReactCoffeeScriptClass', -> render: -> React.createElement Foo - test React.createElement(Outer), 'SPAN', 'foo' + expect(-> + test React.createElement(Outer), 'SPAN', 'foo' + ).toErrorDev([ + 'Outer uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + 'Foo uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.', + ]) it 'renders only once when setting state in componentWillMount', -> renderCount = 0 @@ -537,7 +542,14 @@ describe 'ReactCoffeeScriptClass', -> render: -> React.createElement Bar - test React.createElement(Foo), 'DIV', 'bar-through-context' + expect(-> + test React.createElement(Foo), 'DIV', 'bar-through-context' + ).toErrorDev( + [ + 'Foo uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + 'Bar uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.', + ], + ) if !featureFlags.disableStringRefs it 'supports string refs', -> diff --git a/packages/react/src/__tests__/ReactContextValidator-test.js b/packages/react/src/__tests__/ReactContextValidator-test.js index cbe9cb2641276..3f24e5bed8d18 100644 --- a/packages/react/src/__tests__/ReactContextValidator-test.js +++ b/packages/react/src/__tests__/ReactContextValidator-test.js @@ -66,11 +66,16 @@ describe('ReactContextValidator', () => { let instance; const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); - await act(() => { - root.render( - (instance = current)} />, - ); - }); + await expect(async () => { + await act(() => { + root.render( + (instance = current)} />, + ); + }); + }).toErrorDev([ + 'ComponentInFooBarContext uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + 'Component uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.', + ]); expect(instance.childRef.current.context).toEqual({foo: 'abc'}); }); @@ -139,9 +144,14 @@ describe('ReactContextValidator', () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); - await act(() => { - root.render(); - }); + await expect(async () => { + await act(() => { + root.render(); + }); + }).toErrorDev([ + 'Parent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + 'Component uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.', + ]); expect(constructorContext).toEqual({foo: 'abc'}); expect(renderContext).toEqual({foo: 'abc'}); @@ -187,11 +197,10 @@ describe('ReactContextValidator', () => { await act(() => { root.render(); }); - }).toErrorDev( - '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.', - ); + }).toErrorDev([ + 'ComponentA uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + '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.', + ]); // Warnings should be deduped by component type let container = document.createElement('div'); @@ -206,11 +215,10 @@ describe('ReactContextValidator', () => { await act(() => { root.render(); }); - }).toErrorDev( - '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.', - ); + }).toErrorDev([ + 'ComponentB uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + '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.', + ]); }); // TODO (bvaughn) Remove this test and the associated behavior in the future. @@ -259,9 +267,10 @@ describe('ReactContextValidator', () => { root.render(); }); }).toErrorDev([ - '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.', + 'ParentContextProvider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + 'MiddleMissingContext uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + '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.', + 'ChildContextConsumer uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.', ]); expect(childContext.bar).toBeUndefined(); expect(childContext.foo).toBe('FOO'); @@ -435,10 +444,11 @@ describe('ReactContextValidator', () => { , ); }); - }).toErrorDev( - 'ComponentA declares both contextTypes and contextType static properties. ' + - 'The legacy contextTypes property will be ignored.', - ); + }).toErrorDev([ + 'ParentContextProvider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead', + 'ComponentA uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.', + 'ComponentA declares both contextTypes and contextType static properties. The legacy contextTypes property will be ignored.', + ]); // Warnings should be deduped by component type let container = document.createElement('div'); @@ -461,10 +471,10 @@ describe('ReactContextValidator', () => { , ); }); - }).toErrorDev( - 'ComponentB declares both contextTypes and contextType static properties. ' + - 'The legacy contextTypes property will be ignored.', - ); + }).toErrorDev([ + 'ComponentB declares both contextTypes and contextType static properties. The legacy contextTypes property will be ignored.', + 'ComponentB uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.', + ]); }); // @gate enableRenderableContext || !__DEV__ diff --git a/packages/react/src/__tests__/ReactES6Class-test.js b/packages/react/src/__tests__/ReactES6Class-test.js index 769bf5b9a5daf..3ac0b18e8e753 100644 --- a/packages/react/src/__tests__/ReactES6Class-test.js +++ b/packages/react/src/__tests__/ReactES6Class-test.js @@ -13,6 +13,7 @@ let PropTypes; let React; let ReactDOM; let ReactDOMClient; +let assertConsoleErrorDev; describe('ReactES6Class', () => { let container; @@ -30,6 +31,7 @@ describe('ReactES6Class', () => { React = require('react'); ReactDOM = require('react-dom'); ReactDOMClient = require('react-dom/client'); + ({assertConsoleErrorDev} = require('internal-test-utils')); container = document.createElement('div'); root = ReactDOMClient.createRoot(container); attachedListener = null; @@ -287,6 +289,11 @@ describe('ReactES6Class', () => { className: PropTypes.string, }; runTest(, 'SPAN', 'foo'); + + assertConsoleErrorDev([ + 'Outer uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + 'Foo uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.', + ]); }); } @@ -579,6 +586,10 @@ describe('ReactES6Class', () => { } Foo.childContextTypes = {bar: PropTypes.string}; runTest(, 'DIV', 'bar-through-context'); + assertConsoleErrorDev([ + 'Foo uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + 'Bar uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.', + ]); }); } diff --git a/packages/react/src/__tests__/ReactStrictMode-test.js b/packages/react/src/__tests__/ReactStrictMode-test.js index 6a887fc9d9808..2ab9c3fa18c9b 100644 --- a/packages/react/src/__tests__/ReactStrictMode-test.js +++ b/packages/react/src/__tests__/ReactStrictMode-test.js @@ -18,6 +18,7 @@ let act; let useMemo; let useState; let useReducer; +let assertConsoleErrorDev; const ReactFeatureFlags = require('shared/ReactFeatureFlags'); @@ -28,7 +29,7 @@ describe('ReactStrictMode', () => { ReactDOM = require('react-dom'); ReactDOMClient = require('react-dom/client'); ReactDOMServer = require('react-dom/server'); - act = require('internal-test-utils').act; + ({act, assertConsoleErrorDev} = require('internal-test-utils')); useMemo = React.useMemo; useState = React.useState; useReducer = React.useReducer; @@ -1072,11 +1073,33 @@ describe('context legacy', () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); - await expect(async () => { - await act(() => { - root.render(); - }); - }).toErrorDev( + await act(() => { + root.render(); + }); + + assertConsoleErrorDev([ + 'LegacyContextProvider uses the legacy childContextTypes API ' + + 'which will soon be removed. Use React.createContext() instead. ' + + '(https://react.dev/link/legacy-context)' + + '\n in LegacyContextProvider (at **)' + + '\n in div (at **)' + + '\n in Root (at **)', + 'LegacyContextConsumer uses the legacy contextTypes API which ' + + 'will soon be removed. Use React.createContext() with static ' + + 'contextType instead. (https://react.dev/link/legacy-context)' + + '\n in LegacyContextConsumer (at **)' + + '\n in div (at **)' + + '\n in LegacyContextProvider (at **)' + + '\n in div (at **)' + + '\n in Root (at **)', + 'FunctionalLegacyContextConsumer uses the legacy contextTypes ' + + 'API which will be removed soon. Use React.createContext() ' + + 'with React.useContext() instead. (https://react.dev/link/legacy-context)' + + '\n in FunctionalLegacyContextConsumer (at **)' + + '\n in div (at **)' + + '\n in LegacyContextProvider (at **)' + + '\n in div (at **)' + + '\n in Root (at **)', 'Legacy context API has been detected within a strict-mode tree.' + '\n\nThe old API will be supported in all 16.x releases, but applications ' + 'using it should migrate to the new version.' + @@ -1087,7 +1110,7 @@ describe('context legacy', () => { '\n in LegacyContextProvider (at **)' + '\n in div (at **)' + '\n in Root (at **)', - ); + ]); // Dedupe await act(() => { diff --git a/packages/react/src/__tests__/ReactTypeScriptClass-test.ts b/packages/react/src/__tests__/ReactTypeScriptClass-test.ts index 139a5c01e8d4b..4f4564d356a8c 100644 --- a/packages/react/src/__tests__/ReactTypeScriptClass-test.ts +++ b/packages/react/src/__tests__/ReactTypeScriptClass-test.ts @@ -518,7 +518,10 @@ describe('ReactTypeScriptClass', function() { if (!ReactFeatureFlags.disableLegacyContext) { it('renders based on context in the constructor', function() { - test(React.createElement(ProvideChildContextTypes), 'SPAN', 'foo'); + expect(() => test(React.createElement(ProvideChildContextTypes), 'SPAN', 'foo')).toErrorDev([ + 'ProvideChildContextTypes uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + 'StateBasedOnContext uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.' + ]); }); } @@ -687,8 +690,11 @@ describe('ReactTypeScriptClass', function() { }); if (!ReactFeatureFlags.disableLegacyContext) { - it('supports this.context passed via getChildContext', function() { - test(React.createElement(ProvideContext), 'DIV', 'bar-through-context'); + it('supports this.context passed via getChildContext', () => { + expect(() => test(React.createElement(ProvideContext), 'DIV', 'bar-through-context')).toErrorDev([ + 'ProvideContext uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + 'ReadContext uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.', +] ); }); } diff --git a/packages/react/src/__tests__/createReactClassIntegration-test.js b/packages/react/src/__tests__/createReactClassIntegration-test.js index 8adb37cad0592..330150506c384 100644 --- a/packages/react/src/__tests__/createReactClassIntegration-test.js +++ b/packages/react/src/__tests__/createReactClassIntegration-test.js @@ -10,6 +10,7 @@ 'use strict'; let act; +let assertConsoleErrorDev; let PropTypes; let React; @@ -19,7 +20,7 @@ let createReactClass; describe('create-react-class-integration', () => { beforeEach(() => { jest.resetModules(); - ({act} = require('internal-test-utils')); + ({act, assertConsoleErrorDev} = require('internal-test-utils')); PropTypes = require('prop-types'); React = require('react'); ReactDOMClient = require('react-dom/client'); @@ -336,6 +337,10 @@ describe('create-react-class-integration', () => { await act(() => { root.render(); }); + assertConsoleErrorDev([ + 'Component uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.', + 'Component uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.', + ]); expect(container.firstChild.className).toBe('foo'); });