Skip to content

Commit

Permalink
Warn about legacy context when legacy context is not disabled
Browse files Browse the repository at this point in the history
For environments that still have legacy contexts available, this adds a warning to make the remaining call sites easier to locate and encourage upgrades.

.
  • Loading branch information
kassens committed Jul 9, 2024
1 parent 553e031 commit ede4485
Show file tree
Hide file tree
Showing 10 changed files with 148 additions and 40 deletions.
10 changes: 9 additions & 1 deletion packages/react-dom/src/__tests__/ReactFunctionComponent-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ let PropTypes;
let React;
let ReactDOMClient;
let act;
let assertConsoleErrorDev;

function FunctionComponent(props) {
return <div>{props.name}</div>;
Expand All @@ -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 () => {
Expand Down Expand Up @@ -109,6 +110,10 @@ describe('ReactFunctionComponent', () => {
root.render(<GrandParent test="test" />);
});

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(() => {
Expand Down Expand Up @@ -472,6 +477,9 @@ describe('ReactFunctionComponent', () => {
await act(() => {
root.render(<Parent />);
});
assertConsoleErrorDev([
'Child uses the legacy contextTypes API which will be removed soon. Use React.createContext() with React.useContext() instead.',
]);
expect(el.textContent).toBe('en');
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,27 +227,31 @@ test('handles events on text nodes', () => {
}

const log = [];
ReactNative.render(
<ContextHack>
<Text>
<Text
onTouchEnd={() => log.push('string touchend')}
onTouchEndCapture={() => log.push('string touchend capture')}
onTouchStart={() => log.push('string touchstart')}
onTouchStartCapture={() => log.push('string touchstart capture')}>
Text Content
</Text>
<Text
onTouchEnd={() => 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(
<ContextHack>
<Text>
<Text
onTouchEnd={() => log.push('string touchend')}
onTouchEndCapture={() => log.push('string touchend capture')}
onTouchStart={() => log.push('string touchstart')}
onTouchStartCapture={() => log.push('string touchstart capture')}>
Text Content
</Text>
<Text
onTouchEnd={() => log.push('number touchend')}
onTouchEndCapture={() => log.push('number touchend capture')}
onTouchStart={() => log.push('number touchstart')}
onTouchStartCapture={() => log.push('number touchstart capture')}>
{123}
</Text>
</Text>
</Text>
</ContextHack>,
1,
);
</ContextHack>,
1,
);
}).toErrorDev([
'ContextHack uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
]);

expect(UIManager.createView).toHaveBeenCalledTimes(5);

Expand Down
20 changes: 14 additions & 6 deletions packages/react-reconciler/src/ReactFiberBeginWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
);
}
}
}
}
Expand Down
16 changes: 16 additions & 0 deletions packages/react-reconciler/src/ReactFiberClassComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand Down
16 changes: 16 additions & 0 deletions packages/react-server/src/ReactFizzClassComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand Down
16 changes: 14 additions & 2 deletions packages/react/src/__tests__/ReactCoffeeScriptClass-test.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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', ->
Expand Down
11 changes: 11 additions & 0 deletions packages/react/src/__tests__/ReactES6Class-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ let PropTypes;
let React;
let ReactDOM;
let ReactDOMClient;
let assertConsoleErrorDev;

describe('ReactES6Class', () => {
let container;
Expand All @@ -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;
Expand Down Expand Up @@ -287,6 +289,11 @@ describe('ReactES6Class', () => {
className: PropTypes.string,
};
runTest(<Outer />, '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.',
]);
});
}

Expand Down Expand Up @@ -579,6 +586,10 @@ describe('ReactES6Class', () => {
}
Foo.childContextTypes = {bar: PropTypes.string};
runTest(<Foo />, '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.',
]);
});
}

Expand Down
36 changes: 29 additions & 7 deletions packages/react/src/__tests__/ReactStrictMode-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ let act;
let useMemo;
let useState;
let useReducer;
let assertConsoleErrorDev;

const ReactFeatureFlags = require('shared/ReactFeatureFlags');

Expand All @@ -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;
Expand Down Expand Up @@ -1072,11 +1073,32 @@ describe('context legacy', () => {

const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
await expect(async () => {
await act(() => {
root.render(<Root />);
});
}).toErrorDev(
await act(() => {
root.render(<Root />);
});

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.' +
Expand All @@ -1087,7 +1109,7 @@ describe('context legacy', () => {
'\n in LegacyContextProvider (at **)' +
'\n in div (at **)' +
'\n in Root (at **)',
);
]);

// Dedupe
await act(() => {
Expand Down
12 changes: 9 additions & 3 deletions packages/react/src/__tests__/ReactTypeScriptClass-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.'
]);
});
}

Expand Down Expand Up @@ -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.',
] );
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
'use strict';

let act;
let assertConsoleErrorDev;

let PropTypes;
let React;
Expand All @@ -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');
Expand Down Expand Up @@ -336,6 +337,10 @@ describe('create-react-class-integration', () => {
await act(() => {
root.render(<Outer />);
});
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');
});

Expand Down

0 comments on commit ede4485

Please sign in to comment.