Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[New] Align no-deprecated rule with React 18 deprecations #3548

Merged
merged 1 commit into from
Mar 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
### Added
* [`display-name`]: add `checkContextObjects` option ([#3529][] @JulesBlm)
* [`jsx-first-prop-new-line`]: add `multiprop` option ([#3533][] @haydncomley)
* [`no-deprecated`]: add React 18 deprecations ([#3548][] @sergei-startsev)

### Fixed
* [`no-array-index-key`]: consider flatMap ([#3530][] @k-yle)
* [`jsx-curly-brace-presence`]: handle single and only expression template literals ([#3538][] @taozhou-glean)
* [`no-unknown-property`]: allow `onLoad` on `source` (@ljharb)

[#3548]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3548
[#3538]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3538
[#3533]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3533
[#3530]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3530
Expand Down
25 changes: 24 additions & 1 deletion docs/rules/no-deprecated.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,42 @@ import React, { PropTypes } from 'react';
componentWillMount() { }
componentWillReceiveProps() { }
componentWillUpdate() { }

// React 18 deprecations
import { render } from 'react-dom';
ReactDOM.render(<div></div>, container);

import { hydrate } from 'react-dom';
ReactDOM.hydrate(<div></div>, container);

import {unmountComponentAtNode} from 'react-dom';
ReactDOM.unmountComponentAtNode(container);

import { renderToNodeStream } from 'react-dom/server';
ReactDOMServer.renderToNodeStream(element);
```

Examples of **correct** code for this rule:

```jsx
// when React < 18
ReactDOM.render(<MyComponent />, root);
sergei-startsev marked this conversation as resolved.
Show resolved Hide resolved

// When [1, {"react": "0.13.0"}]
// when React is < 0.14
ReactDOM.findDOMNode(this.refs.foo);

import { PropTypes } from 'prop-types';

UNSAFE_componentWillMount() { }
UNSAFE_componentWillReceiveProps() { }
UNSAFE_componentWillUpdate() { }

ReactDOM.createPortal(child, container);

import { createRoot } from 'react-dom/client';
const root = createRoot(container);
root.unmount();

import { hydrateRoot } from 'react-dom/client';
const root = hydrateRoot(container, <App/>);
```
25 changes: 25 additions & 0 deletions lib/rules/no-deprecated.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ const report = require('../util/report');
const MODULES = {
react: ['React'],
'react-addons-perf': ['ReactPerf', 'Perf'],
'react-dom': ['ReactDOM'],
'react-dom/server': ['ReactDOMServer'],
};

// ------------------------------------------------------------------------------
Expand Down Expand Up @@ -82,6 +84,29 @@ function getDeprecated(pragma) {
'https://reactjs.org/docs/react-component.html#unsafe_componentwillupdate. '
+ 'Use https://github.com/reactjs/react-codemod#rename-unsafe-lifecycles to automatically update your components.',
];
// 18.0.0
// https://reactjs.org/blog/2022/03/08/react-18-upgrade-guide.html#deprecations
deprecated['ReactDOM.render'] = [
'18.0.0',
'createRoot',
'https://reactjs.org/link/switch-to-createroot',
];
deprecated['ReactDOM.hydrate'] = [
'18.0.0',
'hydrateRoot',
'https://reactjs.org/link/switch-to-createroot',
];
deprecated['ReactDOM.unmountComponentAtNode'] = [
'18.0.0',
'root.unmount',
'https://reactjs.org/link/switch-to-createroot',
];
deprecated['ReactDOMServer.renderToNodeStream'] = [
'18.0.0',
'renderToPipeableStream',
'https://reactjs.org/docs/react-dom-server.html#rendertonodestream',
];

return deprecated;
}

Expand Down
126 changes: 124 additions & 2 deletions tests/lib/rules/no-deprecated.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ ruleTester.run('no-deprecated', rule, {
// Not deprecated
'var element = React.createElement(\'p\', {}, null);',
'var clone = React.cloneElement(element);',
'ReactDOM.render(element, container);',
'ReactDOM.unmountComponentAtNode(container);',
Comment on lines -51 to -52
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should be able to keep these test cases with the react version set to 17.999.999

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added one more test to cover this scenario.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant, keep the "valid" tests cases, because these remain valid in react < 18.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant, keep the "valid" cases, because these remain valid in react < 18.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You cannot leave it where it was before, React version isn't specified there.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right - i mean, edit the existing rule to add the react version, so that "a passing test for ReactDOM.render" remains

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

React version can be specified using eslint settings.react.version property which is demonstrated in the added tests. Introducing multiple sources to determine React version doesn't seem like a good idea to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ljharb Is there anything else that prevents the PR from being merged? I believe the requested scenario has been covered:

// React < 18
{
code: `
import { render, hydrate } from 'react-dom';
import { renderToNodeStream } from 'react-dom/server';
ReactDOM.render(element, container);
ReactDOM.unmountComponentAtNode(container);
ReactDOMServer.renderToNodeStream(element);
`,
settings: { react: { version: '17.999.999' } },
},

'ReactDOM.cloneElement(child, container);',
'ReactDOM.findDOMNode(instance);',
'ReactDOM.createPortal(child, container);',
'ReactDOMServer.renderToString(element);',
'ReactDOMServer.renderToStaticMarkup(element);',
{
Expand Down Expand Up @@ -119,6 +119,40 @@ ruleTester.run('no-deprecated', rule, {
let { default: defaultReactExport, ...allReactExports } = React;
`,
},
// React < 18
{
code: `
import { render, hydrate } from 'react-dom';
import { renderToNodeStream } from 'react-dom/server';
ReactDOM.render(element, container);
ReactDOM.unmountComponentAtNode(container);
ReactDOMServer.renderToNodeStream(element);
`,
settings: { react: { version: '17.999.999' } },
},
// React 18 API
{
code: `
import ReactDOM, { createRoot } from 'react-dom/client';
ReactDOM.createRoot(container);
const root = createRoot(container);
root.unmount();
`,
},
{
code: `
import ReactDOM, { hydrateRoot } from 'react-dom/client';
ReactDOM.hydrateRoot(container, <App/>);
hydrateRoot(container, <App/>);
`,
},
{
code: `
import ReactDOMServer, { renderToPipeableStream } from 'react-dom/server';
ReactDOMServer.renderToPipeableStream(<App />, {});
renderToPipeableStream(<App />, {});
`,
},
]),

invalid: parsers.all([
Expand Down Expand Up @@ -454,5 +488,93 @@ ruleTester.run('no-deprecated', rule, {
),
],
},
{
code: `
import { render } from 'react-dom';
ReactDOM.render(<div></div>, container);
`,
errors: [
errorMessage(
'ReactDOM.render',
'18.0.0',
'createRoot',
'https://reactjs.org/link/switch-to-createroot',
{ type: 'ImportDeclaration', line: 2, column: 9 }
),
errorMessage(
'ReactDOM.render',
'18.0.0',
'createRoot',
'https://reactjs.org/link/switch-to-createroot',
{ type: 'MemberExpression', line: 3, column: 9 }
),
],
},
{
code: `
import { hydrate } from 'react-dom';
ReactDOM.hydrate(<div></div>, container);
`,
errors: [
errorMessage(
'ReactDOM.hydrate',
'18.0.0',
'hydrateRoot',
'https://reactjs.org/link/switch-to-createroot',
{ type: 'ImportDeclaration', line: 2, column: 9 }
),
errorMessage(
'ReactDOM.hydrate',
'18.0.0',
'hydrateRoot',
'https://reactjs.org/link/switch-to-createroot',
{ type: 'MemberExpression', line: 3, column: 9 }
),
],
},
{
code: `
import { unmountComponentAtNode } from 'react-dom';
ReactDOM.unmountComponentAtNode(container);
`,
errors: [
errorMessage(
'ReactDOM.unmountComponentAtNode',
'18.0.0',
'root.unmount',
'https://reactjs.org/link/switch-to-createroot',
{ type: 'ImportDeclaration', line: 2, column: 9 }
),
errorMessage(
'ReactDOM.unmountComponentAtNode',
'18.0.0',
'root.unmount',
'https://reactjs.org/link/switch-to-createroot',
{ type: 'MemberExpression', line: 3, column: 9 }
),
],
},
{
code: `
import { renderToNodeStream } from 'react-dom/server';
ReactDOMServer.renderToNodeStream(element);
`,
errors: [
errorMessage(
'ReactDOMServer.renderToNodeStream',
'18.0.0',
'renderToPipeableStream',
'https://reactjs.org/docs/react-dom-server.html#rendertonodestream',
{ type: 'ImportDeclaration', line: 2, column: 9 }
),
errorMessage(
'ReactDOMServer.renderToNodeStream',
'18.0.0',
'renderToPipeableStream',
'https://reactjs.org/docs/react-dom-server.html#rendertonodestream',
{ type: 'MemberExpression', line: 3, column: 9 }
),
],
},
]),
});