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 2e7236b
Show file tree
Hide file tree
Showing 13 changed files with 175 additions and 50 deletions.
10 changes: 8 additions & 2 deletions packages/react-dom/src/__tests__/ReactDOMFiber-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -732,6 +734,10 @@ describe('ReactDOMFiber', () => {
await act(async () => {
root.render(<Parent />);
});
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('<div>bar</div>');
});
Expand Down
20 changes: 14 additions & 6 deletions packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ let ReactDOMFizzServer;
let ReactDOMFizzStatic;
let Suspense;
let SuspenseList;

let assertConsoleErrorDev;
let useSyncExternalStore;
let useSyncExternalStoreWithSelector;
let use;
Expand Down Expand Up @@ -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`
Expand Down Expand Up @@ -1931,6 +1935,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(
<div>
Loading: <b>A</b>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ describe('ReactErrorBoundaries', () => {
let RetryErrorBoundary;
let Normal;
let assertLog;
let assertConsoleErrorDev;

beforeEach(() => {
jest.useFakeTimers();
Expand All @@ -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) {
Expand Down Expand Up @@ -895,6 +895,9 @@ describe('ReactErrorBoundaries', () => {
</ErrorBoundary>,
);
});
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.');
});

Expand Down
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
Loading

0 comments on commit 2e7236b

Please sign in to comment.