Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ Enable the rules that you would like to use.
| | | [react/no-will-update-set-state](docs/rules/no-will-update-set-state.md) | Prevent usage of setState in componentWillUpdate |
| | | [react/prefer-es6-class](docs/rules/prefer-es6-class.md) | Enforce ES5 or ES6 class for React Components |
| | | [react/prefer-exact-props](docs/rules/prefer-exact-props.md) | Prefer exact proptype definitions |
| | | [react/prefer-function-component](docs/rules/prefer-function-component.md) | Prefer function components over class components |
| | 🔧 | [react/prefer-read-only-props](docs/rules/prefer-read-only-props.md) | Require read-only props. |
| | | [react/prefer-stateless-function](docs/rules/prefer-stateless-function.md) | Enforce stateless components to be written as a pure function |
| ✔ | | [react/prop-types](docs/rules/prop-types.md) | Prevent missing props validation in a React component definition |
Expand Down
89 changes: 89 additions & 0 deletions docs/rules/prefer-function-component.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Prefer function components over class components (react/prefer-function-component)

This rule prevents the use of class 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`.

## Rule Details

This rule will flag any React class components that don't use `componentDidCatch`.

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

```jsx
import { Component } from 'react';

class Foo extends Component {
render() {
return <div>{this.props.foo}</div>;
}
}
```

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

```jsx
const Foo = function (props) {
return <div>{props.foo}</div>;
};
```

```jsx
const Foo = ({ foo }) => <div>{foo}</div>;
```

## Rule Options

```js
...
"prefer-function-component": [<enabled>, { "allowComponentDidCatch": <allowComponentDidCatch> }]
...
```

- `enabled`: for enabling the rule. 0=off, 1=warn, 2=error. Defaults to 0.
- `allowComponentDidCatch`: optional boolean set to `true` if you would like to ignore class components using `componentDidCatch` (default to `true`).

### `allowComponentDidCatch`

When `true` (the default) the rule will ignore components that use `componentDidCatch`

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

```jsx
import { Component } from 'react';

class Foo extends Component {
componentDidCatch(error, errorInfo) {
logErrorToMyService(error, errorInfo);
}

render() {
return <div>{this.props.foo}</div>;
}
}
```

When `false` the rule will also flag components that use `componentDidCatch`

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

```jsx
import { Component } from 'react';

class Foo extends Component {
componentDidCatch(error, errorInfo) {
logErrorToMyService(error, errorInfo);
}

render() {
return <div>{this.props.foo}</div>;
}
}
```

### Related rules

- [prefer-stateless-function](./prefer-stateless-function)
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ const allRules = {
'no-will-update-set-state': require('./lib/rules/no-will-update-set-state'),
'prefer-es6-class': require('./lib/rules/prefer-es6-class'),
'prefer-exact-props': require('./lib/rules/prefer-exact-props'),
'prefer-function-component': require('./lib/rules/prefer-function-component'),
'prefer-read-only-props': require('./lib/rules/prefer-read-only-props'),
'prefer-stateless-function': require('./lib/rules/prefer-stateless-function'),
'prop-types': require('./lib/rules/prop-types'),
Expand Down
86 changes: 86 additions & 0 deletions lib/rules/prefer-function-component.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/**
* @fileoverview Enforce function components over class components
* @author Tate Thurston
*/

'use strict';

const docsUrl = require('../util/docsUrl');
const Components = require('../util/Components');
const ast = require('../util/ast');

const COMPONENT_SHOULD_BE_FUNCTION = 'componentShouldBeFunction';
const ALLOW_COMPONENT_DID_CATCH = 'allowComponentDidCatch';
const COMPONENT_DID_CATCH = 'componentDidCatch';

module.exports = {
meta: {
docs: {
description: 'Enforce components are written as function components',
category: 'Stylistic Issues',
recommended: false,
suggestion: false,
url: docsUrl('prefer-function-component')
},
fixable: false,
type: 'problem',
messages: {
[COMPONENT_SHOULD_BE_FUNCTION]:
'Class component should be written as a function'
},
schema: [
{
type: 'object',
properties: {
[ALLOW_COMPONENT_DID_CATCH]: {
default: true,
type: 'boolean'
}
},
additionalProperties: false
}
]
},

create: Components.detect((context, components, utils) => {
const allowComponentDidCatchOption = context.options[0] && context.options[0].allowComponentDidCatch;
const allowComponentDidCatch = allowComponentDidCatchOption !== false;

function shouldPreferFunction(node) {
if (!allowComponentDidCatch) {
return true;
}

const properties = ast
.getComponentProperties(node)
.map(ast.getPropertyName);
return !properties.includes(COMPONENT_DID_CATCH);
}

const detect = (guard) => (node) => {
if (guard(node) && shouldPreferFunction(node)) {
components.set(node, {
[COMPONENT_SHOULD_BE_FUNCTION]: true
});
}
};

return {
ObjectExpression: detect(utils.isES5Component),
ClassDeclaration: detect(utils.isES6Component),
ClassExpression: detect(utils.isES6Component),

'Program:exit'() {
const list = components.list();
Object.values(list).forEach((component) => {
if (component[COMPONENT_SHOULD_BE_FUNCTION]) {
context.report({
node: component.node,
messageId: COMPONENT_SHOULD_BE_FUNCTION
});
}
});
}
};
})
};
Loading