[New Rule] prefer-function-component#3040
[New Rule] prefer-function-component#3040tatethurston wants to merge 2 commits intojsx-eslint:masterfrom
Conversation
|
#2860 doesn't have a "help wanted" or "accepted" label on it - I continue to be unconvinced this is actually a good rule to have. Some things must be class components, and other things are more clear/better as class components; I don't think a lint rule is naunced enough to be appropriate. That said, I did ask you to open the PR, so I'll give it a good faith review. |
ljharb
left a comment
There was a problem hiding this comment.
I wonder if perhaps this could be a more generic "prefer-component-style" rule, that can be used to force class components or force function components?
| Since the addition of hooks, it has been possible to write stateful React components | ||
| using only functions. Mixing both class and function components in a code base adds unnecessary hurdles for sharing reusable logic. | ||
|
|
||
| By default, class components that use `componentDidCatch` are enabled because there is currently no hook alternative for React. This option is configurable via `allowComponentDidCatch`. |
There was a problem hiding this comment.
i'm not clear on why this is an option. A class component that's an error boundary can never be a functional component (pending changes from React itself), so there'd never be any value in this rule warning on it. I think that this option shouldn't be an option, it should just be hardcoded to true.
There was a problem hiding this comment.
Many codebases I've seen don't implement error boundaries from scratch in their code base, instead they import library solutions that use error boundaries internally. I made this configurable (though with the default value of true so most clients can ignore it) for clients who want all class component usage to be flagged. This also provides a migration path should React release functional component error boundaries.
There was a problem hiding this comment.
Also, Preact does offer functional error boundaries via a hook, so this would be valuable for those users.
| suggestion: false, | ||
| url: docsUrl('prefer-function-component') | ||
| }, | ||
| fixable: false, |
There was a problem hiding this comment.
is there a reason this rule isn't fixable?
Any component with a constructor body or class methods, of course, couldn't be fixable, but the example in the readme could.
There was a problem hiding this comment.
Partially fixable seemed odd to me on first thought, but the same logic as used by prefer-stateless-function could be used here.
There was a problem hiding this comment.
Lots of rules are partially fixable; obv it's ideal when things are fully fixable tho.
Actually wait, I'd forgotten about prefer-stateless-function. How is this rule different from that one? https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/prefer-stateless-function.md
There was a problem hiding this comment.
I think #2860 explains the difference well:
The prefer-stateless-function rule is not enough for this case, since it allows class components provided they have a state or additional methods (like handlers defined as a class methods). It is not possible to forbid the use of state altogether (you can disable setState, but defining state is still allowed, silencing prefer-stateless-function), and filtering class methods using eslint rules without interfering with other code is complicated.
There was a problem hiding this comment.
i see, so this is a stronger thing than that, where prefer-stateless-function pushes SFCs when possible, this rule would ban class components entirely in favor of hook-using SFCs. Gotcha.
Yeah that seems reasonable to me, the motivating intent here is to keep consistency in a code base. Looking at the recent work by the Relay team (and their close collaboration with the React team) and the React concurrent mode work my impression is that the longer term direction of React is going to be towards all function components, but that is speculative right now. |
I don't have any expectations here, just something to consider. This can always sit or be closed and reopened in the future. I just want things to be code complete (or close) while I have the time. |
|
Just wanted to add a voice of support for this approach. |
|
Just wanted to pop in to say I'd like to have this lint rule too. As pointed out elsewhere, the biggest value here is in enabling consistency across a codebase and the ability to reuse logic between components easily. In my case, another big use is to use these lint rules to gauge how well we are/are not doing in paying down tech debt over time on an old-ish codebase where we're about 50/50 on function components versus class components. Given the large-ish size of our org and codebase, this has to be automated via tooling so we can create dashboards/reports/etc. |
|
Does the |
No that rule enforces the function definition and the request here is to enforce function components instead of class components. It’s complementary but none overlapping. |
Agree with Tate. This rule, while useful, doesn't solve the issue of preventing the introduction of class components into a codebase. |
59af733 to
865ed16
Compare
069314a to
181c68f
Compare
|
Looks like this PR has gone dormant. I also support the addition of this rule. It would be useful when managing a codebase whose contributors have different levels of familiarity with React, e.g., a mix of FE specialists and mid-stack or backend devs that are more used to class-heavy OO languages. Has anything changed from the maintainer's point of view over the last year? I feel like the greater community has almost fully abandoned classes. |
|
@robwierzbowski nope, nothing's changed there, and ErrorBoundary in particular still is required to be a class component. |
|
That's true. I believe this code allows error boundaries to remain class components. As you discussed above, whether this is hardcoded true or an option, the issue could be addressed. |
380e32c to
51d342b
Compare
This is based on the rule provided in [eslint-plugin-react-prefer-function-component](https://www.npmjs.com/package/eslint-plugin-react-prefer-function-component). This rule is [proposed for eslint-plugin-react](jsx-eslint/eslint-plugin-react#3040), but so far not merged. The goal of the rule is pretty simple. We want to discourage class components like this, as they are not used very often in modern React: ```jsx class Foo extends React.Component { render() { return <div>{this.props.foo}</div>; } } class Bar extends React.PureComponent { render() { return <div>{this.props.bar}</div>; } } ``` And encourage function components like this: ```jsx const Foo = function(props) { return <div>{props.foo}</div>; }; const Bar = ({ bar }) => <div>{bar}</div>; ``` - [Original Rule Source](https://github.com/tatethurston/eslint-plugin-react-prefer-function-component/blob/4682fae095307a73db6bda706ec53a61ad6bd60c/packages/eslint-plugin-react-prefer-function-component/src/prefer-function-component/index.ts) - [Original Rule Tests](https://github.com/tatethurston/eslint-plugin-react-prefer-function-component/blob/main/packages/eslint-plugin-react-prefer-function-component/src/prefer-function-component/index.test.ts) The original eslint-plugin-react plugin lacks a lint rule like this, which is very silly as it has one for preferring class components (which are notably [discouraged by React itself](https://react.dev/reference/react/Component) nowadays). It does have `prefer-stateless-functions`, however I would strongly recommend we mark that rule as unsupported in favor of this rule. If you look at [the tests for prefer-stateless-function](https://github.com/jsx-eslint/eslint-plugin-react/blob/master/tests/lib/rules/prefer-stateless-function.js), you can see how lenient it is in allowing class components (anything with a method other than `render()` is allowed, basically). And it has the usual eslint-plugin-react smells of supporting things from many many many years ago. This rule is much more straight-forward, and generally a better implementation of the concept. We could, alternatively, implement this based on [this rule](https://www.eslint-react.xyz/docs/rules/no-class-component) from `@eslint-react/eslint-plugin` (different from eslint-plugin-react), however I generally prefer the `prefer-function-component` name and the `@eslint-react` rule was generally going to be more complex to implement. AI Disclosure: Generated with Claude Code w/ Opus 4.5, after prep work by me. Tested and reviewed by me. I created the rule via the rulegen tooling, then manually copied over the initial set of test cases myself. I then had Claude create the remaining tests from the original source code. From looking through the tests, I am confident in the quality/accuracy of the ported tests, and will be doing further testing via oxc-ecosystem-ci. Ecosystem CI run: https://github.com/oxc-project/oxc-ecosystem-ci/actions/runs/22334972119 Bsky for example. [main](https://github.com/oxc-project/oxc-ecosystem-ci/actions/runs/22331033071/job/64613773279): ``` Found 0 warnings and 78356 errors. Finished in 1.7s on 1553 files with 629 rules using 4 threads. ``` [this PR](https://github.com/oxc-project/oxc-ecosystem-ci/actions/runs/22334972119/job/64625880558): ``` Found 0 warnings and 78361 errors. Finished in 1.5s on 1553 files with 630 rules using 4 threads. ``` The 5 extra violations are due to this rule, and all 5 are correctly catching class component usage. In terms of performance, there's clearly no noticeable difference here, even on a React codebase of a decent size. --------- Co-authored-by: Cameron Clark <cameron.clark@hey.com>
Adds a new rule to enforce the use of function components over class components. Related issue: #2860
Users wanting to try this lint rule today:
This functionality exists in eslint-plugin-react-prefer-function-component.
npm install eslint-plugin-react-prefer-function-component