diff --git a/packages/react-dom/src/__tests__/ReactFunctionComponent-test.js b/packages/react-dom/src/__tests__/ReactFunctionComponent-test.js index d3b8926ecd3ea..2c1859eac3777 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,10 @@ describe('ReactFunctionComponent', () => { root.render(); }); + assertConsoleErrorDev([ + '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 +477,9 @@ describe('ReactFunctionComponent', () => { await act(() => { root.render(); }); + assertConsoleErrorDev([ + '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-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..9625d5ae89c83 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -1130,12 +1130,20 @@ 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) { + if (disableLegacyContext) { + console.error( + '%s uses the legacy contextTypes API which was removed in React 19. ' + + 'Use React.createContext() with React.useContext() instead.', + getComponentNameFromType(Component) || 'Unknown', + ); + } else { + console.error( + '%s uses the legacy contextTypes API which will be removed soon. ' + + 'Use React.createContext() with React.useContext() instead.', + getComponentNameFromType(Component) || 'Unknown', + ); + } } } } diff --git a/packages/react-reconciler/src/ReactFiberClassComponent.js b/packages/react-reconciler/src/ReactFiberClassComponent.js index 4f7ef8530a908..4189546a0815a 100644 --- a/packages/react-reconciler/src/ReactFiberClassComponent.js +++ b/packages/react-reconciler/src/ReactFiberClassComponent.js @@ -426,6 +426,22 @@ 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.', + 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.', + name, + ); + } } if (typeof instance.componentShouldUpdate === 'function') { diff --git a/packages/react-server/src/ReactFizzClassComponent.js b/packages/react-server/src/ReactFizzClassComponent.js index 6ef87f100b57d..8fa17bbdc41fd 100644 --- a/packages/react-server/src/ReactFizzClassComponent.js +++ b/packages/react-server/src/ReactFizzClassComponent.js @@ -403,6 +403,22 @@ 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.', + 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.', + name, + ); + } } if (typeof instance.componentShouldUpdate === 'function') { 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__/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..0f40118c65caa 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,32 @@ 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.' + + '\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.' + + '\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.' + + '\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 +1109,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.', +] ); }); }