Skip to content

Commit b0f08c4

Browse files
committed
switch to allowErrorBoundary
1 parent c4277d9 commit b0f08c4

File tree

3 files changed

+46
-96
lines changed

3 files changed

+46
-96
lines changed

README.md

Lines changed: 25 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ using only functions.
3434

3535
Leaving the choice between class or function components up to the community is great, but generally within a codebase I want consistency: either we're writing class components and HoCs or we're using function components and hooks. Straddling the two adds unnecessary hurdles for sharing reusable logic.
3636

37-
By default, class components that use `componentDidCatch` or `static getDerivedStateFromError` are enabled because there is currently no hook alternative for React. This is configurable via `allowComponentDidCatch` and `allowGetDerivedStateFromError`.
37+
By default, class components that use `componentDidCatch` or `static getDerivedStateFromError` are allowed because there is currently no hook alternative for React. This is configurable via `allowErrorBoundary`.
3838

3939
This rule is intended to complement the [eslint-plugin-react](https://github.com/yannickcr/eslint-plugin-react) rule set.
4040

@@ -102,7 +102,7 @@ module.exports = {
102102
rules: {
103103
"react-prefer-function-component/react-prefer-function-component": [
104104
"error",
105-
{ allowComponentDidCatch: false },
105+
{ allowErrorBoundary: false },
106106
],
107107
},
108108
};
@@ -142,86 +142,75 @@ const Foo = ({ foo }) => <div>{foo}</div>;
142142

143143
```js
144144
...
145-
"react-prefer-function-component": [<enabled>, { "allowComponentDidCatch": <allowComponentDidCatch>, "allowJsxUtilityClass": <allowJsxUtilityClass> }]
145+
"react-prefer-function-component": [<enabled>, { "allowErrorBoundary": <allowErrorBoundary>, "allowJsxUtilityClass": <allowJsxUtilityClass> }]
146146
...
147147
```
148148

149149
- `enabled`: for enabling the rule. 0=off, 1=warn, 2=error. Defaults to 0.
150-
- `allowComponentDidCatch`: optional boolean. set to `false` if you want to also flag class components that use `componentDidCatch` (defaults to `true`).
151-
- `allowGetDerivedStateFromError`: optional boolean. set to `false` if you want to also flag class components that use `static getDerivedStateFromError` (defaults to `true`).
150+
- `allowErrorBoundary`: optional boolean. set to `false` if you want to also flag class components that use `componentDidCatch` or `static getDerivedStateFromError` (defaults to `true`).
152151
- `allowJsxUtilityClass`: optional boolean. set to `true` if you want to allow classes that contain JSX but aren't class components (defaults to `false`).
153152

154-
### `allowComponentDidCatch`
153+
### `allowErrorBoundary`
155154

156-
When `true` (the default) the rule will ignore components that use `componentDidCatch`
155+
When `true` (the default) the rule will ignore error boundary components that use `componentDidCatch` or `static getDerivedStateFromError`
157156

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

160159
```jsx
161160
import { Component } from "react";
162161

163-
class Foo extends Component {
162+
class ErrorBoundary extends Component {
164163
componentDidCatch(error, errorInfo) {
165164
logErrorToMyService(error, errorInfo);
166165
}
167166

168167
render() {
169-
return <div>{this.props.foo}</div>;
168+
return <div>{this.props.children}</div>;
170169
}
171170
}
172171
```
173172

174-
When `false` the rule will also flag components that use `componentDidCatch`
175-
176-
Examples of **incorrect** code for this rule:
177-
178173
```jsx
179174
import { Component } from "react";
180175

181-
class Foo extends Component {
182-
componentDidCatch(error, errorInfo) {
183-
logErrorToMyService(error, errorInfo);
176+
class ErrorBoundary extends Component {
177+
constructor(props) {
178+
super(props);
179+
this.state = { hasError: false };
180+
}
181+
182+
static getDerivedStateFromError(error) {
183+
return { hasError: true };
184184
}
185185

186186
render() {
187-
return <div>{this.props.foo}</div>;
187+
return <div>{this.state.hasError ? "Error" : this.props.children}</div>;
188188
}
189189
}
190190
```
191191

192-
### `allowGetDerivedStateFromError`
193-
194-
When `true` (the default) the rule will ignore components that use `static getDerivedStateFromError`
192+
When `false` the rule will also flag error boundary components
195193

196-
Examples of **correct** code for this rule:
194+
Examples of **incorrect** code for this rule:
197195

198196
```jsx
199197
import { Component } from "react";
200198

201-
class Foo extends Component {
202-
constructor(props) {
203-
super(props);
204-
this.state = { hasError: false };
205-
}
206-
207-
static getDerivedStateFromError(error) {
208-
return { hasError: true };
199+
class ErrorBoundary extends Component {
200+
componentDidCatch(error, errorInfo) {
201+
logErrorToMyService(error, errorInfo);
209202
}
210203

211204
render() {
212-
return <div>{this.state.hasError ? "Error" : this.props.foo}</div>;
205+
return <div>{this.props.children}</div>;
213206
}
214207
}
215208
```
216209

217-
When `false` the rule will also flag components that use `static getDerivedStateFromError`
218-
219-
Examples of **incorrect** code for this rule:
220-
221210
```jsx
222211
import { Component } from "react";
223212

224-
class Foo extends Component {
213+
class ErrorBoundary extends Component {
225214
constructor(props) {
226215
super(props);
227216
this.state = { hasError: false };
@@ -232,7 +221,7 @@ class Foo extends Component {
232221
}
233222

234223
render() {
235-
return <div>{this.state.hasError ? "Error" : this.props.foo}</div>;
224+
return <div>{this.state.hasError ? "Error" : this.props.children}</div>;
236225
}
237226
}
238227
```

packages/eslint-plugin-react-prefer-function-component/src/prefer-function-component/index.test.ts

Lines changed: 10 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { RuleTester } from "eslint";
22
import rule, {
3-
ALLOW_COMPONENT_DID_CATCH,
4-
ALLOW_GET_DERIVED_STATE_FROM_ERROR,
3+
ALLOW_ERROR_BOUNDARY,
54
ALLOW_JSX_UTILITY_CLASS,
65
COMPONENT_SHOULD_BE_FUNCTION,
76
} from ".";
@@ -180,7 +179,7 @@ const invalidForAllOptions = [
180179
`,
181180
];
182181

183-
const componentDidCatch = [
182+
const errorBoundaries = [
184183
// Extends from Component and uses componentDidCatch
185184
`\
186185
class Foo extends React.Component {
@@ -214,9 +213,6 @@ const componentDidCatch = [
214213
}
215214
};
216215
`,
217-
];
218-
219-
const getDerivedStateFromError = [
220216
// Extends from Component and uses static getDerivedStateFromError
221217
`\
222218
class Foo extends React.Component {
@@ -279,24 +275,18 @@ ruleTester.run("prefer-function-component", rule, {
279275
...validForAllOptions.flatMap((code) => [
280276
{ code },
281277
{ code, options: [{ [ALLOW_JSX_UTILITY_CLASS]: true }] },
282-
{ code, options: [{ [ALLOW_COMPONENT_DID_CATCH]: false }] },
283-
{ code, options: [{ [ALLOW_GET_DERIVED_STATE_FROM_ERROR]: false }] },
278+
{ code, options: [{ [ALLOW_ERROR_BOUNDARY]: false }] },
284279
{
285280
code,
286281
options: [
287282
{
288283
[ALLOW_JSX_UTILITY_CLASS]: true,
289-
[ALLOW_COMPONENT_DID_CATCH]: false,
290-
[ALLOW_GET_DERIVED_STATE_FROM_ERROR]: false,
284+
[ALLOW_ERROR_BOUNDARY]: false,
291285
},
292286
],
293287
},
294288
]),
295-
...componentDidCatch.flatMap((code) => [
296-
{ code },
297-
{ code, options: [{ [ALLOW_JSX_UTILITY_CLASS]: true }] },
298-
]),
299-
...getDerivedStateFromError.flatMap((code) => [
289+
...errorBoundaries.flatMap((code) => [
300290
{ code },
301291
{ code, options: [{ [ALLOW_JSX_UTILITY_CLASS]: true }] },
302292
]),
@@ -316,37 +306,24 @@ ruleTester.run("prefer-function-component", rule, {
316306
{
317307
code,
318308
errors: [{ messageId: COMPONENT_SHOULD_BE_FUNCTION }],
319-
options: [{ [ALLOW_COMPONENT_DID_CATCH]: false }],
309+
options: [{ [ALLOW_ERROR_BOUNDARY]: false }],
320310
},
321311
{
322312
code,
323313
errors: [{ messageId: COMPONENT_SHOULD_BE_FUNCTION }],
324314
options: [
325315
{
326316
[ALLOW_JSX_UTILITY_CLASS]: true,
327-
[ALLOW_COMPONENT_DID_CATCH]: false,
317+
[ALLOW_ERROR_BOUNDARY]: false,
328318
},
329319
],
330320
},
331321
]),
332-
...componentDidCatch.map((code) => ({
333-
code,
334-
options: [
335-
{
336-
[ALLOW_COMPONENT_DID_CATCH]: false,
337-
},
338-
],
339-
errors: [
340-
{
341-
messageId: COMPONENT_SHOULD_BE_FUNCTION,
342-
},
343-
],
344-
})),
345-
...getDerivedStateFromError.map((code) => ({
322+
...errorBoundaries.map((code) => ({
346323
code,
347324
options: [
348325
{
349-
[ALLOW_GET_DERIVED_STATE_FROM_ERROR]: false,
326+
[ALLOW_ERROR_BOUNDARY]: false,
350327
},
351328
],
352329
errors: [
@@ -360,7 +337,7 @@ ruleTester.run("prefer-function-component", rule, {
360337
{
361338
code,
362339
errors: [{ messageId: COMPONENT_SHOULD_BE_FUNCTION }],
363-
options: [{ [ALLOW_COMPONENT_DID_CATCH]: false }],
340+
options: [{ [ALLOW_ERROR_BOUNDARY]: false }],
364341
},
365342
]),
366343
],

packages/eslint-plugin-react-prefer-function-component/src/prefer-function-component/index.ts

Lines changed: 11 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,13 @@ import type { Rule } from "eslint";
55
import type { TSESTree } from "@typescript-eslint/types";
66

77
export const COMPONENT_SHOULD_BE_FUNCTION = "componentShouldBeFunction";
8-
export const ALLOW_COMPONENT_DID_CATCH = "allowComponentDidCatch";
9-
export const ALLOW_GET_DERIVED_STATE_FROM_ERROR =
10-
"allowGetDerivedStateFromError";
8+
export const ALLOW_ERROR_BOUNDARY = "allowErrorBoundary";
119
export const ALLOW_JSX_UTILITY_CLASS = "allowJsxUtilityClass";
1210

1311
type ClassNode = TSESTree.ClassDeclaration | TSESTree.ClassExpression;
1412

1513
type RuleOptions = {
16-
allowComponentDidCatch?: boolean;
17-
allowGetDerivedStateFromError?: boolean;
14+
allowErrorBoundary?: boolean;
1815
allowJsxUtilityClass?: boolean;
1916
};
2017

@@ -36,11 +33,7 @@ const rule = {
3633
{
3734
type: "object",
3835
properties: {
39-
[ALLOW_COMPONENT_DID_CATCH]: {
40-
default: true,
41-
type: "boolean",
42-
},
43-
[ALLOW_GET_DERIVED_STATE_FROM_ERROR]: {
36+
[ALLOW_ERROR_BOUNDARY]: {
4437
default: true,
4538
type: "boolean",
4639
},
@@ -56,33 +49,24 @@ const rule = {
5649

5750
create(context: Rule.RuleContext) {
5851
const options: RuleOptions = context.options[0] ?? {};
59-
const allowComponentDidCatch = options.allowComponentDidCatch ?? true;
60-
const allowGetDerivedStateFromError =
61-
options.allowGetDerivedStateFromError ?? true;
52+
const allowErrorBoundary = options.allowErrorBoundary ?? true;
6253
const allowJsxUtilityClass = options.allowJsxUtilityClass ?? false;
6354

6455
function shouldPreferFunction(node: ClassNode): boolean {
65-
const properties = node.body.body;
66-
const hasComponentDidCatch = properties.some((property) => {
56+
const isErrorBoundary = node.body.body.some((property) => {
6757
if ("key" in property && "name" in property.key) {
68-
return property.key.name === "componentDidCatch";
58+
return (
59+
property.key.name === "componentDidCatch" ||
60+
(property.static &&
61+
property.key.name === "getDerivedStateFromError")
62+
);
6963
}
70-
});
71-
72-
if (hasComponentDidCatch && allowComponentDidCatch) {
7364
return false;
74-
}
75-
76-
const hasGetDerivedStateFromError = properties.some((property) => {
77-
if ("key" in property && "name" in property.key && property.static) {
78-
return property.key.name === "getDerivedStateFromError";
79-
}
8065
});
8166

82-
if (hasGetDerivedStateFromError && allowGetDerivedStateFromError) {
67+
if (isErrorBoundary && allowErrorBoundary) {
8368
return false;
8469
}
85-
8670
return true;
8771
}
8872

0 commit comments

Comments
 (0)