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 10, 2024
1 parent 39e69dc commit 053f755
Show file tree
Hide file tree
Showing 15 changed files with 225 additions and 75 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 @@ -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(
<div>
Loading: <b>A</b>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ describe('ReactDOMServerIntegration', () => {
<PurpleContext>
<ClassChildWithContext />
</PurpleContext>,
2,
);
expect(e.textContent).toBe('purple');
});
Expand All @@ -94,6 +95,7 @@ describe('ReactDOMServerIntegration', () => {
<PurpleContext>
<FunctionChildWithContext />
</PurpleContext>,
2,
);
expect(e.textContent).toBe('purple');
});
Expand All @@ -110,6 +112,7 @@ describe('ReactDOMServerIntegration', () => {
<PurpleContext>
<ClassChildWithoutContext />
</PurpleContext>,
1,
);
expect(e.textContent).toBe('');
});
Expand All @@ -124,6 +127,7 @@ describe('ReactDOMServerIntegration', () => {
<PurpleContext>
<FunctionChildWithoutContext />
</PurpleContext>,
1,
);
expect(e.textContent).toBe('');
});
Expand All @@ -141,6 +145,7 @@ describe('ReactDOMServerIntegration', () => {
<PurpleContext>
<ClassChildWithWrongContext />
</PurpleContext>,
2,
);
expect(e.textContent).toBe('');
});
Expand All @@ -158,6 +163,7 @@ describe('ReactDOMServerIntegration', () => {
<PurpleContext>
<FunctionChildWithWrongContext />
</PurpleContext>,
2,
);
expect(e.textContent).toBe('');
});
Expand All @@ -174,6 +180,7 @@ describe('ReactDOMServerIntegration', () => {
<PurpleContext>
<Child />
</PurpleContext>,
2,
);
expect(e.textContent).toBe('purple');
});
Expand All @@ -190,6 +197,7 @@ describe('ReactDOMServerIntegration', () => {
<Grandchild />
</RedContext>
</PurpleContext>,
2,
);
expect(e.textContent).toBe('red');
});
Expand Down Expand Up @@ -228,7 +236,7 @@ describe('ReactDOMServerIntegration', () => {
text2: PropTypes.string,
};

const e = await render(<Parent />);
const e = await render(<Parent />, 3);
expect(e.querySelector('#first').textContent).toBe('purple');
expect(e.querySelector('#second').textContent).toBe('red');
});
Expand All @@ -254,7 +262,7 @@ describe('ReactDOMServerIntegration', () => {
};
Child.contextTypes = {text: PropTypes.string};

const e = await render(<WillMountContext />);
const e = await render(<WillMountContext />, 2);
expect(e.textContent).toBe('foo');
},
);
Expand All @@ -278,7 +286,8 @@ describe('ReactDOMServerIntegration', () => {
}
const e = await render(
<ForgetfulParent />,
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');
},
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
41 changes: 27 additions & 14 deletions packages/react-reconciler/src/ReactFiberBeginWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@ let didReceiveUpdate: boolean = false;

let didWarnAboutBadClass;
let didWarnAboutContextTypeOnFunctionComponent;
let didWarnAboutContextTypes;
let didWarnAboutGetDerivedStateOnFunctionComponent;
let didWarnAboutFunctionRefs;
export let didWarnAboutReassigningProps: boolean;
Expand All @@ -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;
Expand Down Expand Up @@ -1130,12 +1132,25 @@ 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.',
componentName,
);
} else {
console.error(
'%s uses the legacy contextTypes API which will be removed soon. ' +
'Use React.createContext() with React.useContext() instead.',
componentName,
);
}
}
}
}
}
Expand Down Expand Up @@ -1923,14 +1938,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 = '';
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
Loading

0 comments on commit 053f755

Please sign in to comment.